Como implementar Transactional Outbox em eventos de negócio: guia prático para produção
Guia prático para implementar Transactional Outbox em produção, com modelagem, publicação via polling ou CDC, idempotência, retries e observabilidade.
Max Alex

Em arquiteturas de microserviços, uma alteração de negócio frequentemente precisa produzir um evento para outros serviços. O problema é que salvar dados no banco e publicar no broker são operações distintas. Se uma delas falhar no momento errado, o sistema pode ficar inconsistente.
O Transactional Outbox resolve essa lacuna ao registrar a mudança de negócio e o evento pendente na mesma transação local. Depois do commit, um processo separado publica esse evento de modo recuperável. Este guia apresenta um desenho prático para levar o padrão à produção.
O problema: banco atualizado, evento não publicado
O cenário clássico é a dual write: a aplicação atualiza seu banco e, em seguida, envia uma mensagem ao broker. Entre essas duas ações existe uma janela de falha inevitável.
Imagine que um serviço confirma um pagamento, grava o novo status no banco e sofre uma queda antes de emitir PaymentConfirmed. O pagamento existe, mas o serviço de pedidos nunca é avisado. Um retry do produtor ajuda em falhas conhecidas, porém não resolve uma interrupção do processo depois do commit.
A comunicação por mensageria assíncrona reduz acoplamento entre serviços, mas exige uma estratégia confiável de publicação. Transações distribuídas entre banco e broker podem parecer uma solução, porém normalmente aumentam acoplamento, complexidade operacional e custo de recuperação.
O que é Transactional Outbox e quando usar
Transactional Outbox é um padrão em que o dado de negócio e um registro de evento são persistidos no mesmo banco, usando a mesma transação. Se o commit falhar, nenhum dos dois é salvo. Se funcionar, ambos ficam duráveis.
Um worker, conector ou processo de CDC lê os registros pendentes da outbox e os publica no broker. Caso o broker esteja indisponível, o evento continua armazenado e poderá ser reenviado depois.
O padrão é especialmente indicado para eventos relevantes ao domínio, integrações entre serviços, auditoria de mudanças e fluxos com consistência eventual. Ele não entrega exatamente uma vez de ponta a ponta; o contrato realista é entrega pelo menos uma vez, com consumidores preparados para reentregas.
Desenhe o contrato do evento antes da tabela outbox
Comece pelo evento, não pela infraestrutura. Um evento de negócio descreve algo que já aconteceu, por isso nomes no passado são mais claros: OrderCreated, OrderPaid e InvoiceIssued.
Um envelope consistente costuma incluir event_id, event_type, aggregate_id, occurred_at, version e correlation_id. O payload deve conter somente os dados necessários aos consumidores; não transforme o evento em um espelho do modelo interno ou em um canal para dados sensíveis.
Planeje evolução compatível desde a primeira versão. Campos novos devem ser opcionais quando possível, e mudanças incompatíveis merecem uma versão explícita. Os mesmos cuidados de versionamento de contratos ajudam a evitar que uma mudança de produtor interrompa vários consumidores.
{
"event_id": "f3d2...",
"event_type": "OrderPaid",
"aggregate_id": "order-4821",
"occurred_at": "2026-09-14T10:15:00Z",
"version": 1,
"correlation_id": "req-91ac",
"data": {"amount": 249.90, "currency": "BRL"}
}Modele a tabela outbox para recuperação e rastreabilidade
Uma tabela como outbox_events precisa permitir publicação, investigação e reprocessamento. Campos úteis incluem id, aggregate_type, aggregate_id, event_type, payload, headers, status, created_at, published_at, retry_count e next_attempt_at.
Use um UUID imutável como identificador global do evento. Armazene o payload serializado e cabeçalhos com metadados de rastreabilidade. Estados como PENDING, PROCESSING, PUBLISHED e FAILED facilitam a operação.
Crie índices voltados à consulta do publicador, por exemplo por status e next_attempt_at. Quando a ordem dentro de um agregado for importante, mantenha uma sequência por agregado ou publique usando aggregate_id como chave de partição.
Grave o dado de negócio e o evento na mesma transação
Este é o centro do padrão. Após validar o comando e aplicar a regra de domínio, a aplicação atualiza o agregado e insere o evento na outbox usando a mesma conexão e a mesma transação do banco.
BEGIN;
UPDATE orders SET status = 'PAID' WHERE id = :order_id;
INSERT INTO outbox_events (id, aggregate_id, event_type, payload, status)
VALUES (:event_id, :order_id, 'OrderPaid', :payload, 'PENDING');
COMMIT;O evento só fica elegível para publicação após o commit. Evite enviar diretamente ao broker dentro dessa transação: se o envio for confirmado e o commit falhar, você terá publicado um fato que não existe no banco.
Mantenha a criação do evento próxima da regra que a originou. Assim, aprovar um cliente gera CustomerApproved no mesmo ponto em que o status é alterado, reduzindo o risco de eventos esquecidos em caminhos alternativos da aplicação.
Escolha a estratégia de publicação: polling ou CDC
Há duas abordagens principais. No polling publisher, um worker consulta periodicamente a outbox, reserva um lote de eventos, publica cada item e marca o resultado. É simples de implementar e oferece controle direto sobre lotes, locks e retentativas.
Em bancos que suportam concorrência adequada, o worker pode selecionar registros pendentes com locks de curta duração ou lease de processamento. Isso permite várias instâncias sem que todas publiquem o mesmo evento simultaneamente.
Com Change Data Capture, uma ferramenta lê o log de transações do banco e propaga alterações da tabela outbox. Essa abordagem reduz consultas ativas e pode ser uma boa evolução para maior volume, mas requer maturidade para operar conectores, schemas e retenção do log. Em cenários com Kafka, ferramentas como Debezium são uma escolha comum.
Comece com polling quando simplicidade e controle na aplicação forem prioritários. Avalie CDC quando volume, latência ou padronização de plataforma justificarem a complexidade adicional. Em ambos os casos, reprocessamento seguro continua obrigatório.
Trate duplicidades, ordenação e idempotência
Uma falha pode ocorrer depois que o broker aceitou a mensagem e antes que o publicador registre o evento como publicado. Na retomada, ele poderá reenviar a mesma mensagem. Essa é uma consequência normal da entrega pelo menos uma vez.
O produtor deve reduzir duplicidades desnecessárias com locks ou leases, mas a correção não pode depender apenas disso. Consumidores precisam ser idempotentes: processar o mesmo event_id duas vezes não pode gerar dois efeitos de negócio.
Uma técnica prática é manter uma tabela inbox com chave única em event_id. O consumidor grava o identificador junto da alteração que realiza; se receber o evento novamente, a restrição única transforma a reentrega em uma operação segura. Esse princípio se aplica também a consumidores idempotentes que expõem efeitos por APIs.
Quando a ordem for relevante, trate-a como requisito por agregado, não como promessa global do sistema. Use uma chave de partição baseada no agregado e não deixe um consumidor processar eventos concorrentes daquele mesmo fluxo sem uma política clara de sequência.
Projete retries, falhas permanentes e operação segura
Falhas transitórias, como indisponibilidade de rede ou broker, pedem retry com backoff exponencial e jitter. Registre a próxima tentativa em next_attempt_at para não pressionar uma dependência que já está degradada.
Falhas permanentes são diferentes: payload inválido, contrato incompatível ou destino inexistente não devem ser repetidos indefinidamente. Após um limite definido, marque o evento como FAILED ou encaminhe-o para uma fila de erro auditável.
O processo de replay precisa ser controlado: corrija a causa, registre quem solicitou o reenvio e preserve o histórico de tentativas. Também defina retenção para eventos publicados, arquivamento quando necessário e limpeza que não remova itens ainda exigidos por auditoria ou recuperação.
Monitore a outbox e valide o caminho para produção
Uma outbox sem observabilidade apenas troca perda silenciosa por acúmulo silencioso. Monitore a quantidade de eventos pendentes, a idade do evento pendente mais antigo, taxa de publicação, taxa de falha, número de retries e volume de itens em estado FAILED.
Propague correlation_id e trace_id do comando original até os consumidores. Isso permite relacionar uma ação de negócio ao evento, à publicação e aos efeitos posteriores.
Antes do rollout, teste cenários de falha deliberadamente: queda após o commit, indisponibilidade do broker, timeout depois do envio, duplicidade no consumidor e replay de evento. Defina um SLO de latência de publicação e um alerta, por exemplo, para quando o evento pendente mais antigo ultrapassar o limite aceitável ao negócio.
Checklist de implementação do Transactional Outbox
- O dado de negócio e o registro da outbox são persistidos na mesma transação local.
- O contrato possui identificador único, versão, horário de ocorrência e correlação.
- Há uma estratégia explícita de polling ou CDC, com recuperação após falha.
- Consumidores toleram reentrega e registram ou aplicam deduplicação por
event_id. - Ordenação por agregado é tratada quando o domínio exige sequência.
- Retries, backoff, falhas permanentes e replay têm regras operacionais definidas.
- Métricas, alertas, logs e traces tornam a fila pendente visível.
- Testes comprovam que uma interrupção entre commit e publicação não perde o evento.
Transactional Outbox não elimina a consistência eventual; ele a torna controlável. Ao aceitar a publicação assíncrona, persistir a intenção de emitir o evento e preparar consumidores para duplicidades, sua arquitetura passa a recuperar falhas sem perder fatos importantes do negócio.
Sua arquitetura precisa publicar eventos de negócio sem abrir espaço para inconsistências? Estruture uma avaliação técnica da sua mensageria e priorize os pontos de maior risco antes de escalar a operação.
Max Alex
Criador de conteúdo apaixonado por tecnologia e inovação. Acompanhe nossos artigos para ficar por dentro das melhores estratégias digitais.
Continue lendo

Como implementar templates de mensagem em campanhas transacionais: guia prático para produção
Aprenda a implementar templates de mensagem para campanhas transacionais, do mapeamento de eventos e variáveis à aprovação, integração, testes e monitoramento em produção.
Max Alex

Como implementar webhooks idempotentes em assinaturas recorrentes: guia prático para produção
Aprenda a receber, validar, deduplicar e processar webhooks de assinaturas recorrentes sem cobranças duplicadas, estados inconsistentes ou falhas difíceis de auditar.
Max Alex

Como implementar isolamento por tenant em banco compartilhado: guia prático para produção
Aprenda a implementar isolamento por tenant em banco compartilhado com tenant_id, Row Level Security, controles na aplicação, testes de acesso cruzado e práticas de produção.
Max Alex