Pedro Bertoluchi

Outbox pattern em .NET: por que MassTransit não basta sozinho

MassTransit com RabbitMQ resolve metade do problema; a outra metade aparece quando a transação do banco e a publicação da mensagem deixam de ser atômicas.

6 min de leitura
Voltar para o blog

MassTransit com RabbitMQ é a escolha default de quem precisa de mensageria em .NET, e funciona. O que não funciona é o setup ingênuo: salvar o agregado no banco, publicar o evento logo depois, confiar que tudo deu certo. At-least-once delivery sem outbox é a receita exata para mensagens duplicadas, mensagens perdidas e estados inconsistentes que só aparecem em produção com carga real, quando o broker pisca por trezentos milissegundos no meio de uma transação.

O cenário clássico: o repositório salva o pedido, o commit acontece, o publish para o broker falha por timeout de rede. O agregado está no banco, o evento não saiu, os consumidores nunca souberam. Pior ainda: o publish acontece antes do commit, o broker entrega, o consumidor processa, e a transação do banco faz rollback. Agora o resto do sistema acredita em um estado que nunca existiu. Os dois cenários acontecem em ambiente real, e o segundo é o que destrói confiança em mensageria.

O outbox resolve isso transformando publicação em duas etapas. Na mesma transação do agregado, o handler grava o evento numa tabela de outbox no mesmo banco. Commit atômico: ou agregado e evento existem juntos, ou nenhum dos dois. Um worker dedicado lê a tabela em loop curto, publica no broker, marca como processado. Se o broker estiver fora, o worker tenta de novo até cair. Nenhuma mensagem é perdida, nenhuma é publicada sem o agregado existir.

MassTransit suporta outbox nativo a partir da versão 8 com EF Core, e essa é a configuração que deveria ser default. Mas habilitar a feature não resolve idempotência do lado do consumidor: at-least-once significa que a mensagem pode chegar duas vezes, e o consumidor precisa estar preparado. Chave de deduplicação no payload (id do evento, não id da mensagem do transport) e tabela de mensagens processadas no consumidor são obrigatórias. Sem isso, o outbox apenas garante entrega, não garante processamento único.

A confusão recorrente é equiparar outbox a Event Sourcing. Não é. Outbox resolve confiabilidade de publicação. Event Sourcing resolve fonte da verdade. Os dois podem coexistir (o event store é o próprio outbox), mas adotar Event Sourcing para resolver entrega de mensagem é mover uma montanha para abrir uma porta. Em sistema CRUD com agregado tradicional, outbox sobre o banco existente é a resposta certa, e custa duas tabelas e um worker.

Onde outbox não compensa: domínio puramente CRUD sem integração externa crítica, eventos de telemetria onde perda esporádica é aceitável, sistemas de notificação best-effort. Adicionar outbox nesses casos é overengineering: gasta polling no banco, adiciona latência de até o intervalo do worker, complica deploy. A pergunta que decide é direta: se essa mensagem for perdida silenciosamente, alguém vai descobrir? Se a resposta é sim, outbox. Se é não, publish direto está ótimo.

Tags

  • #dotnet
  • #arquitetura
  • #mensageria

Vamos conversar sobre o seu próximo projeto.

Descreva o desafio em poucas linhas. Em até 1 dia útil eu retorno com uma avaliação técnica e os próximos passos.