Introdução
Vou iniciar uma série sobre o Brighter para explorar o padrão Outbox. Neste artigo, vamos entender como o Brighter implementa esse padrão de forma nativa, garantindo consistência transacional ao publicar mensagens junto com atualizações no banco de dados. Esse padrão é essencial em sistemas distribuídos, onde soluções como Two-Phase Commit (2PC) são inviáveis.
Contexto
Antes de mergulhar no padrão Outbox, vamos entender o problema que ele resolve:
Problema 1: Mensagem Única Após Atualização no Banco
Imagine um sistema que atualiza um registro no banco de dados (ex: PedidoCriado
) e publica uma mensagem. Se o broker de mensagens falhar durante a publicação (ex: problemas de rede), a atualização no banco será bem-sucedida, mas a mensagem será perdida. Isso gera inconsistência entre o estado do sistema e os serviços dependentes.
Uma solução ingênua seria usar retentativas, mas isso pode resultar em mensagens duplicadas ou falhas parciais.
Problema 2: Múltiplas Mensagens com Requisitos de Atomicidade
À medida que o sistema cresce, você pode precisar publicar várias mensagens (ex: PedidoPago
e EstoqueAtualizado
) em uma única transação. Se a segunda mensagem falhar após a primeira ser publicada, os sistemas downstream receberão uma parte das mensagens, violando requisitos de entrega atômica ou idempotência.
Exemplo:
- Atualize um pedido (ex:
pedido.Status = "Pago"
). - Publique
PedidoPago
eEstoqueAtualizado
. - Se
EstoqueAtualizado
falhar apósPedidoPago
ser enviado, o sistema downstream terá um fluxo incompleto.
Isso destaca a necessidade de garantias transacionais ao combinar escritas no banco e publicação de mensagens.
Por Que Soluções Tradicionais Não Funcionam
- Two-Phase Commit (2PC): A maioria dos bancos e brokers (ex: Kafka, RabbitMQ) não suporta 2PC. Mesmo que suportem, isso introduz acoplamento e overhead de performance.
- Retentativas Manuais: Sem isolamento transacional, retentativas podem publicar mensagens duplicadas ou falhar em recuperação de falhas parciais.
Padrão Outbox
O padrão Outbox garante entrega de mensagens pelo menos uma vez (at-least-once delivery) e mantém a consistência transacional entre atualizações no banco e publicação de mensagens. Ele garante que mensagens sejam publicadas somente se a transação no banco for concluída com sucesso, evitando falhas parciais.
Como Funciona
Em vez de publicar mensagens diretamente no gateway de mensageria (ex: Kafka, RabbitMQ), o padrão armazena as mensagens em um banco transacional (ex: PostgreSQL, MySQL) dentro da mesma transação dos dados de negócio. Isso assegura:
- Persistência Atômica: Mensagens e dados são salvos juntos.
- Processo de Retransmissão: Um serviço em segundo plano (sweeper) lê mensagens do Outbox e as publica no gateway.
- Recuperação de Falhas: Se o sistema falhar antes da publicação, a mensagem permanece no Outbox e será reprocessada na recuperação.
Vantagens Principais
- Consistência Transacional: Mensagens e atualizações no banco são persistidas na mesma transação, eliminando a necessidade de transações distribuídas.
- Tolerância a Falhas: Se o gateway de mensageria falhar, as mensagens são reprocessadas até serem entregues.
- Arquitetura Desacoplada: O sistema se concentra em transações locais; a entrega de mensagens é assíncrona.
Implementação do Outbox no Brighter
O Brighter oferece suporte nativo ao padrão Outbox para múltiplos provedores (ex: PostgreSQL, MySQL, DynamoDB).
Como Funciona o Método Post
Internamente
Ao usar Post
para enviar uma mensagem, o Brighter aplica o padrão Outbox em segundo plano:
-
Deposit
:- O método
Post
chamaDeposit
, armazenando a mensagem no Outbox dentro da mesma transação do banco de dados. - Garante que mensagens só sejam persistidas se a transação for concluída com sucesso.
- O método
-
ClearOutbox
:- Após a transação,
ClearOutbox
é chamado com o ID da mensagem. - Publica a mensagem no gateway (ex: RabbitMQ, Kafka) e a remove do Outbox após a entrega.
- Após a transação,
Configuração Padrão
Por padrão, o Brighter usa um Outbox em memória, ideal para testes mas não recomendado para produção, já que mensagens são perdidas se o sistema travar/cair antes da publicação.
Como Configurar
services
.AddBrigther()
.UseInMemoryOutbox() // Ou .UseExternalOutbox(...) para provedores externos
.UseOutboxSweeper(options =>
{
options.TimerInterval = 5; // Intervalo entre verificações (em segundos)
options.MinimumMessageAge = 500; // Idade mínima da mensagem (em milissegundos)
});
Você precisará do pacote Paramore.Brighter.Extensions.Hosting para o sweeper e Paramore.Brighter.Extensions.DependencyInjection para integrar o Brighter com a DI do Microsoft.
Conclusão
O padrão Outbox é fundamental para comunicação confiável em microsserviços. Ao usar o suporte nativo do Brighter:
- Garante consistência transacional sem 2PC.
- Implementa entrega at-least-once com retentativas.
- Desacopla atualizações no banco da publicação de mensagens.
Nos próximos artigos, explorarei provedores de Outbox do Brighter (ex: PostgreSQL, DynamoDB) e configurações avançadas como deduplicação de mensagens e lógica customizada para o sweeper.