Por que você precisa do Transactional Outbox
Se você trabalha com microsserviços ou sistemas distribuídos, é muito provável que já tenha se deparado com (ou escrito) este padrão de código:
// O cenário clássico de Dual Write
async function criarPedido(pedido) {
try {
await db.save(pedido); // 1. Persiste no banco de dados
await messageBroker.publish('pedido-criado', pedido); // 2. Notifica outros serviços
} catch (error) {
log.error("Erro ao processar pedido", error);
}
}
Parece inofensivo e funciona perfeitamente em ambiente local. Mas, em produção, esse código é uma fábrica de inconsistências. Estamos lidando aqui com o problema do Dual Write. Você está tentando garantir a consistência entre dois sistemas de armazenamento distintos (o Banco de Dados e o Message Broker) sem uma transação global que una os dois.
O risco é claro: se o banco de dados confirma a gravação, mas a rede falha ou a aplicação reinicia antes da publicação no Kafka/RabbitMQ, você criou um "registro fantasma". O pedido existe no banco, mas nenhum outro serviço (Pagamento, Estoque, Notificação) sabe disso. O fluxo de negócio morre silenciosamente.
O que é 2PC (Two-Phase Commit)?
É um protocolo de transação distribuída que garante que todos os participantes (bancos, filas) comitem ou façam rollback juntos. Embora resolva a consistência, ele é complexo, bloqueante e impacta severamente a latência e a disponibilidade do sistema, sendo geralmente desencorajado em microsserviços modernos. A solução robusta para evitar o Dual Write sem a complexidade do 2PC é o Transactional Outbox Pattern.
Usando ACID a seu favor
O Transactional Outbox resolve o problema eliminando a segunda chamada de rede durante o fluxo crítico. Em vez de publicar diretamente no broker, você persiste a intenção de publicar.
O fluxo muda para: No mesmo banco de dados onde você salva os dados de negócio (tabela Pedidos), você cria uma tabela auxiliar chamada Outbox.
- Abre uma transação local.
- Insere o pedido na tabela Pedidos.
- Insere o payload do evento (JSON) na tabela Outbox.
- Comita a transação.
Graças às propriedades ACID do seu banco relacional, essa operação é atômica. Ou tudo é salvo, ou nada é salvo. Garantimos que nenhum dado de negócio seja persistido sem o respectivo evento agendado.
O Relay: Tirando o dado do banco
Com o evento salvo, precisamos de um processo assíncrono para ler a tabela Outbox e efetivamente publicar no Broker. Existem duas abordagens principais:
- Polling Publisher: Um job simples que faz consultas periódicas (SELECT * FROM Outbox WHERE processado = false), envia para o broker e atualiza o status. É fácil de implementar, mas adiciona carga ao banco e latência na entrega.
- Transaction Log Tailing (CDC): A abordagem ideal para alta escala. Ferramentas leem diretamente o log de transações do banco de dados. Assim que o registro entra na tabela Outbox, ele é capturado e enviado.
O que é CDC (Change Data Capture)?
É uma técnica que identifica e captura mudanças nos dados de um banco de dados (inserts, updates, deletes) em tempo real, lendo o log de transações (como o Binlog do MySQL ou WAL do Postgres). Ferramentas como o Debezium são o padrão da indústria para isso.
Trade-offs e Cuidados
Adotar o Outbox Pattern introduz complexidade operacional. Você agora tem novas tabelas e componentes de infraestrutura (como o Debezium) para gerenciar. Além disso, esse padrão garante a entrega "At-Least-Once" (Pelo menos uma vez). Se o processo de Relay falhar após enviar para o Kafka, mas antes de marcar como processado no banco, ele enviará a mensagem novamente ao reiniciar.
O que é Idempotência?
É a propriedade de uma operação poder ser aplicada várias vezes sem alterar o resultado além da aplicação inicial. No contexto de mensageria, significa que seu consumidor deve ser capaz de receber a mesma mensagem duplicada sem causar efeitos colaterais indesejados (como cobrar o cliente duas vezes).
Conclusão
Parar de fazer "Dual Writes" ingênuos é um passo necessário de maturidade na engenharia de software. O Transactional Outbox desacopla sua regra de negócio da infraestrutura de mensageria e garante que a integridade dos seus dados não dependa da estabilidade momentânea da rede.