Apache Kafka para desenvolvedores web: conceitos e casos de uso práticos
1. Por que desenvolvedores web precisam entender Kafka?
1.1. A arquitetura monolítica vs. microsserviços: o papel da mensageria assíncrona
Desenvolvedores web acostumados com aplicações monolíticas muitas vezes enfrentam gargalos quando precisam escalar. Em um monólito, uma requisição HTTP geralmente executa tudo em um único processo — desde a validação até o banco de dados. Com microsserviços, surge a necessidade de comunicação entre componentes. Kafka entra como um backbone assíncrono, desacoplando produtores e consumidores sem bloqueios.
# Exemplo: Monólito vs. Kafka em microsserviços
# Monólito: requisição síncrona bloqueante
POST /api/pedido HTTP/1.1
Response: 200 OK (pedido processado, email enviado, estoque atualizado)
# Com Kafka: fluxo assíncrono
POST /api/pedido HTTP/1.1 → Produz evento "pedido_criado" no Kafka
Consumidor 1: Serviço de Email → processa em paralelo
Consumidor 2: Serviço de Estoque → processa em paralelo
1.2. Kafka como cola entre frontend, backend e sistemas externos
Kafka atua como um barramento de eventos que conecta frontends (via WebSocket ou proxies reversos), backends (APIs REST/GraphQL) e sistemas legados. Um evento gerado no frontend pode ser consumido por múltiplos serviços sem acoplamento.
1.3. Diferenças cruciais: filas tradicionais (RabbitMQ) vs. log distribuído (Kafka)
| Característica | RabbitMQ | Kafka |
|---|---|---|
| Modelo | Fila (consume e remove) | Log (retenção por tempo/tamanho) |
| Persistência | Opcional | Por padrão, durável |
| Replay de mensagens | Não suporta nativamente | Sim, via offsets |
| Throughput | Centenas de milhares/seg | Milhões/seg |
Kafka é ideal para streams de eventos e replay histórico; RabbitMQ é melhor para roteamento complexo de mensagens pontuais.
2. Conceitos fundamentais do Kafka para quem vem do mundo web
2.1. Tópicos, partições e offsets: a estrutura de dados que parece um banco
Um tópico é como uma tabela em um banco de dados. As partições são os "shards" físicos. Cada mensagem recebe um offset sequencial — similar a um ID auto-incrementado.
# Estrutura conceitual
Tópico: "cliques_usuario"
Partição 0: [offset 0] [offset 1] [offset 2] ...
Partição 1: [offset 0] [offset 1] [offset 2] ...
2.2. Produtores, consumidores e grupos de consumidores: paralelo com requisições HTTP
- Produtor: equivalente a um
POSTque envia dados ao Kafka. - Consumidor: equivalente a um
GETcontínuo (polling) que lê mensagens. - Grupo de consumidores: balanceamento de carga — cada partição é atribuída a um consumidor do grupo.
# Exemplo: Grupo de consumidores
Grupo "processadores_pedidos":
Consumidor A → Partição 0
Consumidor B → Partição 1
Consumidor C → Partição 2
2.3. Garantias de entrega (at-least-once, exactly-once) e implicações para aplicações web
- At-least-once: padrão — mensagem pode ser entregue mais de uma vez (idempotência necessária no consumidor).
- Exactly-once: suportado com transações Kafka e idempotência do produtor, mas com custo de performance.
- At-most-once: mensagens podem ser perdidas se o consumidor falhar após o recebimento.
Para aplicações web financeiras, prefira exactly-once; para analytics, at-least-once é suficiente.
3. Configurando Kafka no ambiente de desenvolvimento web
3.1. Subindo um cluster Kafka local com Docker Compose (Zookeeper + Broker)
# docker-compose.yml
version: '3'
services:
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
Execute: docker-compose up -d
3.2. Conectando uma aplicação Node.js ao Kafka com bibliotecas oficiais
// Instalação: npm install kafkajs
const { Kafka } = require('kafkajs')
const kafka = new Kafka({
clientId: 'web-app',
brokers: ['localhost:9092']
})
// Produtor
const producer = kafka.producer()
await producer.connect()
await producer.send({
topic: 'eventos-web',
messages: [
{ key: 'user123', value: JSON.stringify({ acao: 'clique', pagina: '/home' }) }
]
})
// Consumidor
const consumer = kafka.consumer({ groupId: 'web-group' })
await consumer.connect()
await consumer.subscribe({ topic: 'eventos-web', fromBeginning: true })
await consumer.run({
eachMessage: async ({ topic, partition, message }) => {
console.log(`Offset ${message.offset}: ${message.value.toString()}`)
}
})
3.3. Comandos essenciais do CLI
# Criar tópico
kafka-topics --create --topic eventos-web --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1
# Listar tópicos
kafka-topics --list --bootstrap-server localhost:9092
# Produzir mensagens
kafka-console-producer --topic eventos-web --bootstrap-server localhost:9092
> {"acao": "teste"}
# Consumir mensagens
kafka-console-consumer --topic eventos-web --bootstrap-server localhost:9092 --from-beginning
4. Padrões de integração entre Kafka e aplicações web
4.1. Event sourcing: registrando ações do usuário como eventos imutáveis
Em vez de salvar o estado atual do usuário, armazene cada ação como um evento no Kafka. O estado é reconstruído reprocessando os eventos.
# Evento de alteração de email
Tópico: "eventos_usuario"
Partição 0:
offset 0: { tipo: "USUARIO_CRIADO", email: "a@b.com", id: 1 }
offset 1: { tipo: "EMAIL_ALTERADO", novo_email: "c@d.com", id: 1 }
4.2. CQRS com Kafka: separando comandos de escrita e consultas de leitura
Comandos (escrita) vão para um tópico Kafka. Um serviço projeta os dados em um banco de leitura otimizado (ex: Elasticsearch, Redis).
# Fluxo CQRS
POST /api/comando → Kafka (tópico "comandos") → Serviço de Comando → Banco de Escrita
Serviço de Projeção → Kafka (tópico "eventos") → Banco de Leitura
GET /api/consulta → Banco de Leitura
4.3. Webhooks vs. Kafka: quando usar cada abordagem
- Webhooks: ideais para notificações simples e externas (ex: pagamento confirmado → chamar URL do parceiro).
- Kafka: melhor para fluxos internos de alta frequência, replay e múltiplos consumidores.
Use webhooks para integrações externas; Kafka para streams internos e event sourcing.
5. Casos de uso práticos no ecossistema web
5.1. Rastreamento de cliques e analytics em tempo real (frontend → Kafka → dashboard)
// Frontend envia evento via fetch ou WebSocket
fetch('/api/track', {
method: 'POST',
body: JSON.stringify({ evento: 'clique', elemento: 'botao-comprar', timestamp: Date.now() })
})
// Backend produz para Kafka
app.post('/api/track', async (req, res) => {
await producer.send({ topic: 'cliques', messages: [{ value: JSON.stringify(req.body) }] })
res.status(202).send()
})
// Consumidor alimenta dashboard em tempo real (ex: Spark Streaming ou Kafka Streams)
5.2. Sincronização de cache (Redis) e invalidação via eventos do Kafka
Quando um dado é atualizado no banco, publique um evento no Kafka. Consumidores invalidam ou atualizam o cache Redis.
# Evento de invalidação
Tópico: "cache_invalidation"
{ chave: "produto:123", acao: "atualizar" }
# Consumidor Redis
consumer.run({
eachMessage: async ({ message }) => {
const { chave } = JSON.parse(message.value)
await redis.del(chave) // invalida cache
}
})
5.3. Orquestração de jobs assíncronos: envio de e-mails, processamento de imagens, webhooks
# Produtor: job de envio de email
producer.send({
topic: 'jobs-email',
messages: [{ value: JSON.stringify({ to: 'user@example.com', template: 'boas-vindas' }) }]
})
# Consumidor: processa e envia email
consumer.run({
eachMessage: async ({ message }) => {
const job = JSON.parse(message.value)
await emailService.send(job.to, job.template)
}
})
6. Tratamento de falhas e resiliência em aplicações web com Kafka
6.1. Estratégias de retry e dead letter queues (DLQ) para mensagens com falha
Crie um tópico separado para mensagens que falharam após N tentativas.
# Estrutura de retry
Tópico: "jobs-email"
Tópico: "jobs-email-retry" (com TTL maior)
Tópico: "jobs-email-dlq" (mensagens mortas)
# Lógica no consumidor
try {
await processarMensagem(message)
} catch (error) {
if (tentativas < 3) {
await producer.send({ topic: 'jobs-email-retry', messages: [message] })
} else {
await producer.send({ topic: 'jobs-email-dlq', messages: [message] })
}
}
6.2. Lidando com partições offline e rebalanceamento de consumidores
Quando um consumidor cai, o grupo rebalanceia — as partições do consumidor falho são reassignadas. Para minimizar impacto, use session.timeout.ms adequado e processamento idempotente.
6.3. Monitoramento básico: lag de consumidores e throughput
# Verificar lag de consumidores via CLI
kafka-consumer-groups --bootstrap-server localhost:9092 --group web-group --describe
# Saída esperada:
# GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
# web-group eventos-web 0 150 200 50
Lag alto indica que consumidores não estão acompanhando a produção. Aumente partições ou consumidores.
7. Boas práticas e armadilhas comuns para desenvolvedores web
7.1. Serialização e evolução de schemas com Avro/Protobuf vs. JSON simples
JSON é fácil, mas não possui schema rígido. Use Avro com Schema Registry para evolução segura:
# Vantagens do Avro
- Schemas versionados
- Tamanho menor que JSON
- Compatibilidade direta/retroativa
# Exemplo de schema Avro
{
"type": "record",
"name": "Clique",
"fields": [
{"name": "usuario_id", "type": "string"},
{"name": "pagina", "type": "string"}
]
}
7.2. Tamanho de mensagens e tuning de produtores/consumidores para latência web
- Mensagens grandes (>1MB): aumente
max.message.bytesno broker e no produtor. - Latência: use
linger.ms(tempo de espera para batch) ebatch.size— trade-off entre latência e throughput. - Compressão: ative
compression.type: snappypara reduzir tráfego.
# Configuração de produtor para baixa latência
await producer.send({
topic: 'eventos-rapidos',
messages: [message],
acks: 1, # apenas líder confirma
compression: 'snappy'
})
7.3. Segurança básica: autenticação SASL e criptografia TLS em ambientes web
Em produção, nunca use PLAINTEXT. Configure SASL/SCRAM e TLS:
# Exemplo de configuração Kafka com SASL
security.protocol=SASL_SSL
sasl.mechanism=SCRAM-SHA-256
sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \
username="user" password="pass";
Referências
- Documentação oficial do Apache Kafka — Guia completo sobre conceitos, configuração e operação do Kafka.
- KafkaJS - Biblioteca Node.js para Kafka — Documentação oficial da biblioteca KafkaJS com exemplos práticos para Node.js.
- Confluent Developer - Kafka Tutorials — Tutoriais práticos sobre Kafka, incluindo integração com aplicações web e microsserviços.
- Event Sourcing and CQRS with Kafka — Artigo da Confluent explicando padrões de event sourcing e CQRS com Kafka.
- Kafka Security Basics (SASL/TLS) — Tutorial prático de configuração de segurança Kafka com SASL e TLS.
- Dead Letter Queue Pattern in Kafka — Artigo técnico sobre implementação de DLQ no Kafka para tratamento de falhas.
- Monitoring Kafka Consumer Lag — Guia da Datadog sobre monitoramento de lag e métricas de performance do Kafka.