Spring para mensageria — o panorama

TL;DR

O ecossistema Spring oferece três projetos complementares para mensageria: Spring Kafka (cliente idiomático para Apache Kafka), Spring AMQP (cliente para RabbitMQ via protocolo AMQP) e Spring Cloud Stream (abstração de mais alto nível que cobre ambos e outros brokers com modelo funcional). Escolher a camada certa evita over-engineering e dependências desnecessárias.

O que é

Quando um projeto Spring precisa se comunicar de forma assíncrona por mensagens, há mais de um caminho. O ecossistema oferece projetos em diferentes níveis de abstração:

ProjetoNívelBroker(s) primário(s)
Spring for Apache KafkaCliente diretoApache Kafka
Spring AMQPCliente diretoRabbitMQ (AMQP 0-9-1 e 1.0)
Spring Cloud StreamAbstração de bindersKafka, RabbitMQ, Pulsar, Kinesis…
Spring IntegrationFramework EIPQualquer canal/adapter

Spring Integration implementa os padrões de integração empresarial (EIP) de Gregor Hohpe — pipes, filtros, routers, transformadores — e é o projeto mais antigo e abrangente. Ele não tem nota dedicada neste galho, mas vale saber que existe; o restante do galho foca nos três primeiros.

Por que importa

Toda plataforma de mensageria expõe uma API de baixo nível (producer/consumer Kafka, channel AMQP) que pode ser usada diretamente. O Spring adiciona:

  • Gerenciamento de ciclo de vida — contêineres de listener que sobem e descem com o contexto.
  • Conversão de mensagens — serialização/desserialização automática de objetos Java.
  • Integração com transações — coordenação com @Transactional.
  • Tratamento de erros padronizado — dead-letter topics/queues, retry declarativo.
  • Observabilidade — métricas Micrometer sem instrumentação manual.

Escolher a abstração errada tem custo real:

  • Usar Spring Cloud Stream quando você só precisa de Kafka adiciona dependências, configuração extra e um modelo mental mais complexo.
  • Usar o cliente Kafka bruto quando você quer suportar dois brokers duplica código de infraestrutura.

Como funciona

Spring Kafka

Spring for Apache Kafka (versão estável atual: 4.1.x; compatível com Spring Boot 3.x/Java 17+) aplica os padrões do Spring ao produtor e consumidor Kafka nativos.

KafkaTemplate encapsula o KafkaProducer e fornece métodos síncronos e assíncronos para envio:

// Envio simples
kafkaTemplate.send("meu-topico", "chave", payload);
 
// Envio com callback
kafkaTemplate.send("meu-topico", payload)
    .whenComplete((result, ex) -> {
        if (ex != null) log.error("Falha no envio", ex);
    });

@KafkaListener transforma um método em consumidor gerenciado pelo contêiner de listener:

@KafkaListener(topics = "meu-topico", groupId = "meu-grupo")
public void consumir(String mensagem, @Header(KafkaHeaders.RECEIVED_KEY) String chave) {
    // processa mensagem
}

O contêiner gerencia threads, rebalanceamento de partições, confirmação de offset e retry com dead-letter topic (DLT).

Spring AMQP

Spring AMQP (versão estável atual: 4.1.x) aplica o mesmo padrão ao protocolo AMQP, tendo RabbitMQ como implementação de referência. Suporta também AMQP 1.0 e RabbitMQ Streams.

RabbitTemplate é o equivalente ao KafkaTemplate para o mundo AMQP:

// Envio para exchange com routing key
rabbitTemplate.convertAndSend("minha-exchange", "routing.key", payload);
 
// Request/Reply síncrono
RespostaDto resposta = (RespostaDto)
    rabbitTemplate.convertSendAndReceive("exchange", "rk", pedido);

@RabbitListener marca métodos consumidores:

@RabbitListener(queues = "minha-fila")
public void processar(PedidoDto pedido) {
    // processa pedido
}

A diferença conceitual central em relação ao Kafka: no AMQP as mensagens são roteadas por exchanges e routing keys para queues (filas com semântica de consumo e exclusão), enquanto no Kafka são publicadas em topics com partitions (log imutável e retentivo).

Spring Cloud Stream

Spring Cloud Stream (versão estável atual: 5.0.x) introduz uma camada de abstração chamada binder que desacopla o código de negócio do broker específico. O mesmo código de aplicação roda sobre Kafka, RabbitMQ, Apache Pulsar, Amazon Kinesis, Azure Event Hubs ou Google PubSub trocando apenas a dependência do binder.

O modelo funcional usa interfaces Java padrão (Function, Consumer, Supplier) como definição das funções de mensageria:

// Consumidor puro (entrada, sem saída)
@Bean
public Consumer<PedidoDto> processarPedido() {
    return pedido -> log.info("Recebido: {}", pedido);
}
 
// Transformador (entrada → saída)
@Bean
public Function<PedidoDto, NotaFiscalDto> emitirNota() {
    return pedido -> gerarNota(pedido);
}
 
// Fonte (sem entrada, produz mensagens)
@Bean
public Supplier<String> gerarEvento() {
    return () -> "evento-" + System.currentTimeMillis();
}

O framework cria os bindings (ligadores) automaticamente a partir dos nomes dos beans. A configuração do broker vai para application.yml — o código não cita Kafka ou Rabbit.

Spring Integration (EIP) é o projeto mais antigo: implementa padrões de pipes-and-filters para fluxos complexos de integração entre sistemas. Está fora do escopo deste galho.

Na prática

Tabela: projeto → quando usar

SituaçãoProjeto recomendado
Só usa Kafka e precisa de controle fino (partições, headers, transações exatas)Spring Kafka
Só usa RabbitMQ, com roteamento por exchanges e filasSpring AMQP
Precisa suportar múltiplos brokers ou trocar broker sem reescrever códigoSpring Cloud Stream
Fluxos de integração complexos (EIP: splitter, aggregator, router)Spring Integration
Aprendendo Kafka no Spring Boot, galho 14Spring Kafka (foco deste galho)

Esboço mínimo — Spring Kafka

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
</dependency>
@Service
public class ProdutorKafka {
    private final KafkaTemplate<String, String> kafkaTemplate;
 
    public void publicar(String topico, String chave, String valor) {
        kafkaTemplate.send(topico, chave, valor);
    }
}

Esboço mínimo — Spring AMQP

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
@Service
public class ProdutorAmqp {
    private final RabbitTemplate rabbitTemplate;
 
    public void publicar(String exchange, String routingKey, Object payload) {
        rabbitTemplate.convertAndSend(exchange, routingKey, payload);
    }
}

Esboço mínimo — Spring Cloud Stream (Kafka binder)

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-stream-binder-kafka</artifactId>
</dependency>
@Bean
public Consumer<String> processar() {
    return msg -> System.out.println("Recebido: " + msg);
}
# application.yml
spring.cloud.stream.bindings.processar-in-0.destination: meu-topico
spring.cloud.stream.bindings.processar-in-0.group: meu-grupo

Armadilhas

(1) Usar Spring Cloud Stream quando o cliente direto bastava

Spring Cloud Stream é poderoso, mas adiciona uma camada de indireção. Se a aplicação só usa Kafka e não há plano de trocar de broker, o Spring Kafka direto é mais simples: configuração mais direta, menor superfície de abstração, acesso fácil a offsets e headers Kafka nativos. A abstração do binder cobra um preço em legibilidade quando você precisa depurar comportamentos Kafka específicos (transações exatas, rebalanceamento manual, headers customizados).

(2) Misturar abstrações no mesmo serviço

Usar KafkaTemplate (Spring Kafka) e ao mesmo tempo definir Function<> beans do Spring Cloud Stream no mesmo contexto Spring causa conflitos de configuração: os dois tentam gerenciar contêineres de listener com propriedades potencialmente sobrepostas. Escolha uma camada por serviço e seja consistente.

Em entrevista

Frase pronta (inglês)

“Spring’s messaging ecosystem has three main layers. Spring Kafka and Spring AMQP are direct clients that give you idiomatic Spring abstractions — KafkaTemplate, RabbitTemplate, and listener annotations — over the native broker APIs. Spring Cloud Stream sits one level higher, using a binder model to decouple your business logic from the specific broker; you write plain Java functions and swap binders in configuration. Choosing between them is a trade-off between control and portability: the direct clients give you lower-level access and simpler mental models when you’re committed to one broker, while Cloud Stream shines when portability or multi-broker support matters.”

Vocabulário

PortuguêsInglês
cliente (direto)client
abstraçãoabstraction
ligadorbinder
modelo funcionalfunctional model
ouvintelistener
gabarito / templatetemplate
filaqueue
tópicotopic
partiçãopartition
troca (AMQP)exchange
chave de roteamentorouting key
fila de mensagens mortasdead-letter queue / dead-letter topic

Veja também

Referências