Se você quer aprender Kafka de verdade…
👉 precisa simular problema real.
Aqui você vai montar um mini sistema de pedidos com:
produção de eventos
falhas controladas
retry automático
DLQ
paralelismo com partitions
1. Cenário do projeto (caso de uso real)
Vamos simular um e-commerce simples:
Order Service → envia pedido
↓
Kafka → distribui eventos
↓
Payment Service → processa pagamento
↓
Notification Service → envia confirmação2. Estrutura dos serviços
kafka-lab/
├── order-service
├── payment-service
├── notification-service
├── docker-compose.yml3. Subindo Kafka com Docker
version: '3'
services:
zookeeper:
image: confluentinc/cp-zookeeper
environment:
ZOOKEEPER_CLIENT_PORT: 2181
kafka:
image: confluentinc/cp-kafka
depends_on:
- zookeeper
ports:
- "9092:9092"
environment:
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:90924. Configuração base (Spring Boot)
application.yml
spring:
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: order-group
auto-offset-reset: earliest
producer:
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer5. Criando o modelo de evento
public record OrderEvent(
String orderId,
double value,
String status
) {}6. Producer (Order Service)
@Service
@RequiredArgsConstructor
public class OrderProducer {
private final KafkaTemplate<String, Object> kafkaTemplate;
public void sendOrder(OrderEvent event) {
kafkaTemplate.send("order-topic", event.orderId(), event);
}
}7. Consumer (Payment Service)
Agora começa o interessante.
@KafkaListener(topics = "order-topic", groupId = "payment-group")
public void process(OrderEvent event) {
if (event.value() < 0) {
throw new RuntimeException("Pagamento inválido");
}
System.out.println("Pagamento processado: " + event.orderId());
}8. Implementando Retry (automático)
Configuração avançada
@Bean
public ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory(
ConsumerFactory<String, Object> consumerFactory,
KafkaTemplate<Object, Object> template) {
var factory = new ConcurrentKafkaListenerContainerFactory<String, Object>();
factory.setConsumerFactory(consumerFactory);
var recoverer = new DeadLetterPublishingRecoverer(template);
var errorHandler = new DefaultErrorHandler(
recoverer,
new FixedBackOff(2000L, 3)
);
factory.setCommonErrorHandler(errorHandler);
return factory;
}👉 Resultado:
tenta 3 vezes
espera 2s entre tentativas
9. Dead Letter Queue (DLQ)
👉 Quando falhar após retry → vai para DLQ
Nome padrão:
order-topic.DLTConsumer da DLQ (Notification Service)
@KafkaListener(topics = "order-topic.DLT")
public void handleFailure(OrderEvent event) {
System.out.println("Falha definitiva: " + event.orderId());
}10. Partitions (escala real)
Criar tópico com partitions:
kafka-topics.sh --create \
--topic order-topic \
--partitions 3 \
--replication-factor 1 \
--bootstrap-server localhost:9092👉 Agora você tem paralelismo real.
11. Consumidor concorrente
@KafkaListener(
topics = "order-topic",
groupId = "payment-group",
concurrency = "3"
)👉 Cada thread pega uma partition.
12. Garantindo ordem (chave)
kafkaTemplate.send("order-topic", event.orderId(), event);👉 Garante ordem por pedido.
13. Idempotência (obrigatório)
if (repository.existsByEventId(event.orderId())) {
return;
}👉 Evita duplicação.
14. Teste prático (laboratório)
Criar endpoint no Order Service:
@PostMapping("/order")
public void createOrder(@RequestBody OrderEvent event) {
producer.sendOrder(event);
}Testes:
✔ Caso normal
{
"orderId": "1",
"value": 100
}👉 Vai processar normalmente
❌ Caso erro (ativa retry + DLQ)
{
"orderId": "2",
"value": -10
}Vai:
tentar 3 vezes
falhar
cair na DLQ
15. Fluxo completo
Order Service
↓
Kafka (order-topic)
↓
Payment Service
↓ (erro)
Retry (3x)
↓
DLQ (order-topic.DLT)
↓
Notification Service16. Boas práticas (nível produção)
✔ Sempre usar DLQ
✔ Sempre usar retry controlado
✔ Sempre usar chave
✔ Idempotência obrigatória
✔ Monitorar lag do consumer
Erros comuns
❌ Não usar DLQ
❌ Não tratar erro
❌ Não pensar em concorrência
❌ Não usar partitions
❌ Não testar falha
Conclusão
Kafka não é só fila.
É sistema distribuído.
Se usar certo:
escala
desacopla
aguenta carga
Se usar errado: vira bomba-relógio.
