Parte V — Java e Spring Boot
Producer com Spring Kafka
KafkaTemplate na prática — topic, key, value, serialização, callbacks, headers e correlationId.
Nesta página
Os capítulos anteriores explicaram conceitos que valem para qualquer client Kafka. A partir daqui, a Parte V mostra como esses conceitos aparecem em código Java real, usando Spring Kafka — começando pelo lado que publica eventos.
KafkaTemplate
KafkaTemplate
KafkaTemplate<K, V> é a abstração do Spring Kafka para publicar mensagens — o equivalente, para produção
de eventos, do JdbcTemplate para acesso a banco. Ele encapsula o KafkaProducer nativo do client Java,
expondo uma API mais simples e integrada ao restante do Spring (injeção de dependência, propriedades de
configuração, métricas).
@Service
public class PagamentoEventPublisher {
private final KafkaTemplate<String, PagamentoAprovadoEvent> kafkaTemplate;
public PagamentoEventPublisher(KafkaTemplate<String, PagamentoAprovadoEvent> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
public void publicar(PagamentoAprovadoEvent evento) {
kafkaTemplate.send("pagamentos.aprovados", evento.getContaId(), evento);
}
}
Topic, Key e Value
O send mais comum recebe três argumentos: o tópico, a key (Capítulo 4 — decide a partition de destino e a
ordenação relativa) e o value (o payload do evento). Overloads com dois argumentos (send(topic, value))
publicam sem key, sujeitos à distribuição aproximadamente uniforme entre partitions.
Nunca esqueça a key em eventos que exigem ordenação
Um refactor que remove a key "para simplificar o código" silenciosamente destrói a ordenação relativa entre eventos da mesma entidade (Capítulo 4). Trate a key como parte do contrato do tópico, documentada junto com ele — não como um detalhe implícito do código do producer.
Serialização
O Spring Kafka delega a serialização de key e value a classes Serializer<T> configuradas via
spring.kafka.producer.key-serializer / value-serializer. As opções mais comuns em produção:
Estratégias de serialização
| Formato | Característica |
|---|---|
JSON (JsonSerializer) | Simples, legível, mas sem verificação de schema — mudanças incompatíveis só quebram em runtime |
| Avro (com Schema Registry) | Schema versionado e compatibilidade verificada em build/publish time; payload binário compacto |
| Protobuf | Similar ao Avro em garantias de schema, com forte tipagem no Java gerado a partir do .proto |
Times que ainda não têm Schema Registry geralmente começam com JSON e migram para Avro/Protobuf quando o número de consumidores cresce o suficiente para que mudanças incompatíveis de payload se tornarem um risco real de quebra entre serviços de times diferentes.
Callbacks: tratando o resultado do envio
send() é assíncrono — retorna um CompletableFuture<SendResult<K, V>> imediatamente, sem esperar a
confirmação do broker. Ignorar esse retorno significa não saber se a publicação teve sucesso.
kafkaTemplate.send("pagamentos.aprovados", evento.getContaId(), evento)
.whenComplete((result, exception) -> {
if (exception != null) {
log.error("Falha ao publicar evento de pagamento", exception);
} else {
log.debug("Evento publicado no offset {}", result.getRecordMetadata().offset());
}
});
"Chamei o send(), então o evento foi publicado"
Chamar send() apenas enfileira a mensagem para envio assíncrono — não garante que ela chegou ao broker.
Sem tratar o callback (ou usar .get() de forma síncrona, com o custo de latência que isso implica), falhas
de publicação passam silenciosamente despercebidas, exatamente o cenário de dual write discutido no
Capítulo 12.
Headers e correlationId
Além de key e value, uma mensagem Kafka carrega headers — metadados arbitrários, tipicamente usados para rastreabilidade e integração entre serviços, sem poluir o payload de negócio.
ProducerRecord<String, PagamentoAprovadoEvent> record =
new ProducerRecord<>("pagamentos.aprovados", evento.getContaId(), evento);
record.headers().add("correlationId", correlationId.getBytes(StandardCharsets.UTF_8));
kafkaTemplate.send(record);
Dica de entrevista
Mencionar correlationId propagado via headers demonstra conhecimento prático de observabilidade em
sistemas distribuídos: ele permite rastrear uma requisição desde a chamada HTTP original, passando pelo
evento Kafka, até os consumers que o processam — essencial para debugar um fluxo que atravessa múltiplos
serviços (aprofundado no Capítulo 15).
Tratamento de falhas do producer
Falhas de publicação se dividem nas mesmas categorias do Capítulo 9: transitórias (broker temporariamente
indisponível — o client Kafka já faz retry automático internamente, configurável via retries e
delivery.timeout.ms) e permanentes (mensagem maior que max.request.size, serialização que lança
exceção). O callback onFailure é o lugar certo para logar, alertar, ou — em casos críticos — persistir a
mensagem falha para reenvio manual, já que o send() não bloqueia a aplicação esperando esse resultado.
Como aparece em entrevistas
"Como você garantiria que um evento foi publicado com sucesso no Kafka?" é a pergunta prática mais comum
sobre producer. A resposta esperada cita o retorno assíncrono do send(), o tratamento do callback (não
apenas chamar e seguir em frente), e — para o cenário de consistência com banco de dados — o outbox pattern
do Capítulo 12, já que nenhum callback de producer resolve o problema de dual write sozinho.
Relação com sistemas reais
Publicando compra autorizada com rastreabilidade
O autorizacao-service, ao aprovar uma compra no cartão, publica o evento CompraAutorizada usando
cartaoId como key (ordenação por cartão, Capítulo 4), propaga o correlationId da requisição HTTP
original via header, e trata o callback do send() registrando uma métrica de falha de publicação — que
alimenta um alerta se a taxa de falha ultrapassar um limiar (Capítulo 15).
Resumo
KafkaTemplate encapsula o producer Kafka no Spring Boot: send(topic, key, value) publica de forma
assíncrona, retornando um CompletableFuture que deve ser tratado via callback para saber se a publicação
teve sucesso. Serialização (JSON, Avro, Protobuf) e headers (como correlationId, para rastreabilidade)
completam o contrato de uma publicação bem-feita. Nenhum desses mecanismos, isoladamente, resolve
consistência com o banco de dados — para isso, o padrão é o outbox (Capítulo 12).
Pode vir a seguir
Prováveis follow-ups: "o que acontece se o send() falhar silenciosamente sem callback tratado?" e "como
você propagaria um correlationId de uma chamada HTTP até o consumer do outro lado do Kafka?".