Descrição: Sair da teoria e colocar a mão na massa: veja como Exchanges, Filas e Bindings funcionam de verdade no RabbitMQ, como publicar e consumir mensagens com Spring AMQP, e como lidar com falhas usando acknowledgments, retries e Dead Letter Queues em um cenário real de manutenção e logística.
No artigo sobre Arquitetura Orientada a Eventos, falei do Event Bus como uma peça abstrata do diagrama. Na prática, essa peça costuma ser um broker de mensageria de verdade — e o RabbitMQ é um dos mais usados no mercado, principalmente em cenários onde cada mensagem precisa chegar a um destino certo, com confirmação de entrega e controle fino de roteamento.
Diferente do Kafka, que é otimizado para streams de eventos de altíssimo volume e retenção longa, o RabbitMQ brilha em cenários de mensageria clássica: filas de trabalho, roteamento complexo entre serviços e garantias de entrega ponto a ponto. É exatamente o tipo de ferramenta que aparece o tempo todo em sistemas de manutenção e ordens de serviço, onde cada mensagem representa uma ação que precisa ser processada — não apenas um evento a ser transmitido.
Producer: a aplicação que publica a mensagem. Ela nunca envia diretamente para uma fila — ela envia para uma Exchange.
Exchange: o roteador. Recebe a mensagem do producer e decide, com base em regras, para quais filas ela deve ser copiada.
Queue (Fila): onde as mensagens realmente ficam armazenadas até serem consumidas.
Binding: a regra que conecta uma Exchange a uma Queue, normalmente através de uma routing key.
Consumer: a aplicação que lê e processa as mensagens de uma fila.
Direct: entrega a mensagem para a fila cuja routing key seja exatamente igual à
informada pelo producer. Uso típico: rotear service-order.created só para quem cadastrou
interesse exatamente nessa chave.
Topic: permite padrões com curingas (* e #) na routing key —
por exemplo, service-order.* captura tanto service-order.created quanto
service-order.closed.
Fanout: ignora a routing key e entrega a mensagem para todas as filas associadas — útil para broadcast, como notificar simultaneamente analytics, auditoria e cache.
Headers: roteia com base em atributos (headers) da mensagem em vez da routing key, para regras mais complexas.
Configurando a Exchange, a fila e o binding declarativamente:
@Configuration
public class RabbitConfig {
@Bean
public TopicExchange serviceOrderExchange() {
return new TopicExchange("service-order.exchange");
}
@Bean
public Queue maintenanceQueue() {
return QueueBuilder.durable("queue.maintenance")
.withArgument("x-dead-letter-exchange", "service-order.dlx")
.build();
}
@Bean
public Binding maintenanceBinding() {
return BindingBuilder.bind(maintenanceQueue())
.to(serviceOrderExchange())
.with("service-order.*");
}
}
Publicando uma mensagem:
@Service
public class ServiceOrderEventPublisher {
private final RabbitTemplate rabbitTemplate;
public void publishCreated(ServiceOrder order) {
rabbitTemplate.convertAndSend(
"service-order.exchange",
"service-order.created",
new ServiceOrderCreatedMessage(order.getId(), order.getAssetId())
);
}
}
E consumindo:
@Component
public class MaintenanceListener {
@RabbitListener(queues = "queue.maintenance")
public void handle(ServiceOrderCreatedMessage message) {
// idempotência: verifica se essa OS já foi processada antes de agir
maintenanceService.scheduleTechnician(message.orderId());
}
}
Por padrão, o Spring AMQP usa ack automático: assim que o método do listener retorna sem lançar exceção, a mensagem é confirmada e removida da fila. Se o método lançar uma exceção antes de terminar, a mensagem volta para a fila (ou vai para a Dead Letter Queue, dependendo da configuração) — ela não se perde simplesmente porque o consumer travou no meio do processamento.
Em cenários mais sensíveis, vale trocar para ack manual, confirmando a mensagem só depois que o efeito colateral (gravar no banco, chamar outro serviço) realmente aconteceu com sucesso:
listener:
simple:
acknowledge-mode: manual
Nem toda mensagem consegue ser processada — um payload malformado, uma dependência externa fora do ar,
um bug no consumer. Sem tratamento, isso pode gerar um loop infinito de reprocessamento. A solução é
configurar uma Dead Letter Exchange (DLX): depois de um número máximo de tentativas,
a mensagem é automaticamente redirecionada para uma fila separada (queue.dlq), onde pode
ser inspecionada manualmente sem travar o fluxo principal.
@Bean
public Queue maintenanceQueue() {
return QueueBuilder.durable("queue.maintenance")
.withArgument("x-dead-letter-exchange", "service-order.dlx")
.withArgument("x-dead-letter-routing-key", "service-order.dead")
.withArgument("x-message-ttl", 60000)
.build();
}
Idempotência sempre: RabbitMQ garante entrega at-least-once por padrão — uma mensagem pode, em cenários de falha de rede, ser entregue mais de uma vez. O consumer precisa lidar com isso sem duplicar efeitos (abrir duas OS para o mesmo evento, por exemplo).
Filas duráveis + mensagens persistentes: sem isso, um restart do broker apaga tudo que estava na fila.
Prefetch count: limita quantas mensagens um consumer recebe antes de confirmar as anteriores, evitando que uma instância fique sobrecarregada enquanto outras ficam ociosas.
Monitoramento da fila: acompanhar o tamanho das filas (via management UI ou métricas exportadas) é o sinal mais rápido de que um consumer está lento ou parado.
Não é uma escolha de "melhor ou pior", é uma escolha de encaixe. RabbitMQ tende a ser mais simples de operar em cenários de fila de trabalho com roteamento rico e baixa/média taxa de mensagens. Kafka tende a se sair melhor quando o volume é massivo, é preciso reprocessar o histórico completo de eventos (replay) ou vários consumidores independentes precisam ler o mesmo stream em velocidades diferentes. Em muitos sistemas reais, os dois convivem: Kafka para o backbone de eventos de domínio, RabbitMQ para filas de trabalho pontuais entre serviços específicos.
RabbitMQ deixa de ser assustador assim que você enxerga suas quatro peças básicas: Exchange, Queue, Binding e Consumer. O resto — tipos de exchange, acknowledgments, Dead Letter Queues — são apenas ajustes finos sobre esse esqueleto simples. Em sistemas de manutenção e logística, onde cada mensagem perdida pode significar uma ordem de serviço que nunca chegou ao técnico certo, entender essas peças na prática é o que diferencia uma integração frágil de uma que aguenta o dia a dia de produção.