SkillsTecnológicas
Menu
Back-end

Transactional Outbox: como publicar eventos com segurança

Aprenda como o padrão Transactional Outbox evita a perda de eventos ao gravar dados de negócio e mensagens na mesma transação do banco.

Marcos RodriguesPublicado em 25 de agosto de 2026Atualizado em 24 de agosto de 202614 min de leitura
Profissionais gravam dados e evento juntos antes da publicação assíncrona

Um serviço confirma um pedido no banco de dados e, logo depois, tenta publicar o evento para estoque, pagamento e entrega.

O primeiro passo funciona, mas a aplicação falha antes do segundo. Para o serviço de pedidos, a compra existe; para o restante do sistema, nada aconteceu.

O Transactional Outbox resolve essa lacuna ao gravar a alteração de negócio e o evento pendente na mesma transação local. Um processo separado lê a outbox e publica a mensagem posteriormente.

Assim, o sistema não depende de uma gravação no banco e de um envio ao broker acontecerem como se fossem uma única operação distribuída.

Neste guia, você entenderá o problema de dual write, o funcionamento do padrão, as diferenças entre polling e Change Data Capture, como lidar com duplicidade e ordenação e quais custos precisam ser considerados antes da implementação.

O que é Transactional Outbox?

Transactional Outbox é um padrão de consistência para aplicações que precisam modificar seus próprios dados e avisar outros componentes por mensagem ou evento.

Em vez de publicar diretamente durante a transação de negócio, a aplicação registra a mensagem em uma tabela ou coleção de saída — a outbox — no mesmo banco.

Depois do commit, um relay independente encontra os registros pendentes e os envia ao broker. Se o envio falhar, ele tenta novamente.

A documentação de padrões da AWS apresenta justamente essa solução para a inconsistência criada quando banco e mensageria são atualizados separadamente.

O padrão não torna o banco e o broker parte da mesma transação. Ele reduz o problema a uma transação ACID local, que o banco já sabe controlar, e transforma a publicação em um processo assíncrono recuperável.

O problema de dual write

Dual write acontece quando uma única decisão de negócio exige gravações em dois sistemas independentes.

Criar o pedido no PostgreSQL e publicar “PedidoCriado” em um broker são duas operações.

Cada uma pode ter sucesso ou falhar sem conhecer o resultado definitivo da outra.

BancoPublicaçãoConsequência
SucessoSucessoEstado e evento permanecem coerentes
SucessoFalhaO pedido existe, mas consumidores não ficam sabendo
FalhaSucessoConsumidores reagem a um pedido que não existe
FalhaFalhaNenhuma alteração é confirmada

A janela pode durar milissegundos e ainda assim importar. Queda do processo, timeout, indisponibilidade do broker ou perda da resposta podem interromper o fluxo exatamente entre os dois passos.

Em arquiteturas de microsserviços, esse tipo de falha parcial é parte normal do ambiente.

Por que trocar a ordem das operações não resolve

Publicar primeiro e gravar depois apenas inverte o risco. Se a mensagem for entregue e o banco rejeitar o commit, consumidores executarão ações baseadas em um fato que não foi confirmado.

Gravar primeiro e publicar depois evita esse cenário, mas permite que o evento seja perdido.

Tentar compensar com um bloco de código também não fecha todas as janelas.

A aplicação pode cair antes de registrar que precisa desfazer, ou a própria compensação pode falhar. Retentativas ajudam com falhas transitórias, mas, sem um registro durável da intenção, o sistema não sabe com segurança o que ficou pendente.

Como o Transactional Outbox funciona

  1. A aplicação inicia uma transação no banco local.
  2. Altera o estado do negócio, como criar um pedido.
  3. Insere na outbox um evento com identidade e payload.
  4. Confirma as duas gravações no mesmo commit.
  5. Um relay busca ou captura o novo registro.
  6. O relay publica a mensagem no broker.
  7. Após confirmação, marca o registro como publicado ou permite sua limpeza.
  8. Consumidores processam a mensagem de forma idempotente.

Se a transação falhar, não há pedido nem evento. Se ela concluir, ambos existem, mesmo que o broker esteja indisponível. A indisponibilidade atrasa a entrega, mas não apaga a intenção de publicar.

Estrutura de uma tabela outbox

CampoFinalidade
event_idIdentidade única usada para rastreio e deduplicação
aggregate_idIdentifica a entidade de negócio, como pedido ou conta
aggregate_typeIndica o tipo de entidade ou destino lógico
event_typeNome estável do acontecimento
payloadDados necessários ao consumidor
occurred_atMomento em que o fato ocorreu
schema_versionVersão do contrato do evento
published_atControle opcional de publicação
attemptsQuantidade opcional de tentativas

A estrutura varia conforme o relay. Uma solução baseada em polling costuma precisar de status, tentativas e bloqueio. Uma solução por captura do log pode tratar cada inserção como imutável e acompanhar a posição fora da tabela.

Em ambos os casos, o identificador do evento deve permanecer estável em todas as retentativas.

A atomicidade está na transação local

O ponto decisivo é inserir o registro da outbox usando a mesma conexão e a mesma transação que alteram os dados de negócio.

Chamar outro serviço, banco ou endpoint para gravar a outbox recria o dual write que o padrão deveria eliminar.

BEGIN;

INSERT INTO pedidos (...);

INSERT INTO outbox_events
  (event_id, aggregate_id, event_type, payload, occurred_at)
VALUES
  (...);

COMMIT;
Dados de negócio e evento são colocados juntos na mesma transação

A orientação da Microsoft para implementar outbox com Azure Cosmos DB segue o mesmo princípio: estado e evento são persistidos atomicamente antes de o evento ser encaminhado.

O mecanismo concreto muda entre bancos relacionais e NoSQL, mas a garantia necessária permanece.

O papel do relay de mensagens

O relay é o componente que transforma registros duráveis em mensagens publicadas.

Ele pode rodar no mesmo processo da aplicação, em um worker separado ou em uma plataforma de captura de mudanças. Separá-lo costuma melhorar escala, recuperação e isolamento.

Esse componente precisa continuar do ponto correto após reinício, limitar concorrência, repetir falhas transitórias e não deixar um registro defeituoso bloquear todos os seguintes.

Também deve distinguir “o broker recusou” de “o broker recebeu, mas a confirmação se perdeu”, pois o segundo caso pode gerar publicação repetida.

Opção 1: Polling Publisher

No polling, um worker consulta periodicamente registros não publicados. Ele reserva um lote, envia os eventos e atualiza o status.

É simples de entender e funciona mesmo quando não há acesso ao log transacional do banco.

  • Vantagens: implementação direta, pouca infraestrutura adicional e controle explícito de lotes e retentativas.
  • Cuidados: consultas frequentes, contenção entre workers, latência determinada pelo intervalo e necessidade de recuperar locks abandonados.

Use seleção com bloqueio compatível com o banco, como reservar linhas sem esperar pelas que outro worker já assumiu.

Processar cada lote em uma transação longa pode manter locks por tempo demais; normalmente a reserva é curta e o envio acontece fora dela, aceitando que uma confirmação perdida possa causar repetição.

Opção 2: Change Data Capture

Com Change Data Capture, um conector observa o log de transações e envia as inserções da outbox ao pipeline. A aplicação apenas grava o evento; não precisa consultar a tabela repetidamente.

O Outbox Event Router do Debezium captura mudanças na tabela e transforma cada registro em mensagem roteada.

Essa abordagem pode oferecer baixa latência e boa escala, mas exige operar conectores, permissões de leitura do log, offsets, compatibilidade de esquema e recuperação do pipeline.

Processo de relay retira eventos da outbox e os encaminha ao broker

Polling ou CDC: como escolher?

CritérioPollingCDC
Complexidade inicialMenorMaior
LatênciaDepende do intervaloGeralmente próxima do tempo real
Carga no bancoConsultas periódicasLeitura do log transacional
InfraestruturaWorker e bancoConector e pipeline de CDC
Controle da aplicaçãoAltoParte fica na plataforma
Escala operacionalExige particionar e coordenar workersDepende da capacidade do conector e do log

Polling costuma ser suficiente para cargas moderadas e equipes que valorizam simplicidade. CDC faz sentido quando a organização já domina a tecnologia, precisa de menor latência ou publica grande volume.

A escolha deve considerar operação, não apenas velocidade em um teste local.

A publicação ainda pode ser duplicada

O relay pode publicar uma mensagem, falhar antes de marcar o registro e publicá-la novamente após reiniciar. Por isso, Transactional Outbox evita perda entre estado e intenção de publicação, mas não oferece exatamente uma entrega ponta a ponta.

Consumidores devem registrar o event_id já processado ou realizar uma alteração naturalmente idempotente.

A identidade precisa ser verificada na mesma transação do efeito local do consumidor.

Caso contrário, ele pode marcar a mensagem como vista e falhar antes de aplicar o negócio, ou aplicar o negócio e falhar antes de registrar a deduplicação.

Como preservar a ordem dos eventos

Um pedido pode gerar “Criado”, “Pago” e “Cancelado”. Publicá-los fora de ordem deixa consumidores em estado incorreto. Ordenação global geralmente custa paralelismo; o requisito comum é preservar a sequência por agregado.

  • grave aggregate_id em cada evento;
  • use essa identidade como chave de particionamento no broker;
  • mantenha uma sequência ou versão por agregado quando necessário;
  • processe eventos do mesmo agregado de forma serial ou com controle otimista;
  • defina como o consumidor reage a uma versão atrasada.

O commit do banco determina uma ordem local, mas concorrência e relays paralelos podem alterá-la na publicação.

A garantia precisa ser planejada de ponta a ponta, dentro do escopo realmente necessário.

Falhas, retentativas e recuperação

O relay deve repetir falhas transitórias com espera progressiva e variação aleatória, evitando pressionar um broker indisponível. Limites de concorrência e circuit breaker podem proteger tanto banco quanto mensageria durante incidentes.

Um evento que falha continuamente precisa ir para quarentena ou receber um estado de erro investigável.

Ignorá-lo silenciosamente quebra a promessa do padrão; bloquear toda a fila por causa dele também pode paralisar o sistema.

Defina reprocessamento manual, alertas e critérios de descarte autorizados.

Limpeza e retenção da outbox

Uma tabela que cresce sem limite aumenta índices, backups e tempo de consulta. Registros confirmados podem ser removidos ou arquivados depois de uma janela compatível com auditoria e recuperação.

Faça a limpeza em lotes pequenos para não criar locks longos nem picos de I/O.

Não apague apenas por idade se ainda houver eventos pendentes. Em CDC, confirme que o conector avançou antes de reduzir a retenção do log ou da tabela.

Políticas de backup também devem preservar a relação entre dados de negócio e eventos durante restaurações.

Contrato e evolução dos eventos

A outbox transporta um contrato que pode sobreviver a várias versões da aplicação. Use nomes estáveis, versão explícita e campos suficientes para o consumidor agir sem depender de detalhes internos do produtor.

Evite serializar diretamente uma entidade do ORM, pois uma mudança de banco pode alterar o evento sem intenção.

Prefira mudanças compatíveis, como adicionar campos opcionais. Quando uma alteração for incompatível, publique nova versão e mantenha consumidores antigos durante a migração.

Informações de correlação e causa ajudam a reconstruir a jornada entre eventos.

Observabilidade do fluxo

  • quantidade de eventos pendentes e idade do mais antigo;
  • tempo entre commit e publicação;
  • taxa de publicação, retentativas e erros permanentes;
  • tamanho da tabela e velocidade de limpeza;
  • posição e atraso do conector CDC;
  • duplicidades detectadas pelos consumidores;
  • eventos em quarentena e tempo de resolução.

Inclua event_id, aggregate_id, correlation_id e causation_id em logs estruturados, sem registrar payload sensível.

Com OpenTelemetry, traces podem relacionar a transação original, o relay e o consumidor. Combine isso com uma estratégia de observabilidade e monitoramento que alerte pela idade da fila, não somente por erros do processo.

Segurança e privacidade

O payload da outbox fica no banco e atravessa infraestrutura de mensageria. Inclua apenas os dados necessários, aplique controles de acesso e criptografia compatíveis com a classificação da informação e defina retenção.

Dados pessoais duplicados em eventos tornam correção e exclusão mais difíceis.

Proteja o relay com permissões mínimas: leitura e atualização da outbox, além de publicação apenas nos destinos autorizados.

Em integrações externas, assinatura e validação de webhooks continuam necessárias; o padrão garante a intenção de enviar, não a identidade de quem recebe.

Principais benefícios

  • Consistência: estado de negócio e intenção de publicar são confirmados juntos.
  • Recuperação: indisponibilidade do broker gera atraso, não perda silenciosa.
  • Desacoplamento: a transação local não precisa aguardar consumidores.
  • Auditoria: eventos pendentes e publicados podem ser rastreados.
  • Compatibilidade: funciona com diferentes bancos e brokers.
  • Escala gradual: relay pode evoluir de polling simples para CDC quando necessário.

Limitações e custos

  • consistência entre serviços continua eventual;
  • mensagens podem ser publicadas mais de uma vez;
  • consumidores precisam ser idempotentes;
  • ordenação exige desenho por agregado ou partição;
  • a tabela requer índices, retenção e limpeza;
  • polling adiciona consultas, enquanto CDC adiciona infraestrutura;
  • contratos de evento precisam de governança;
  • restaurações e migrações exigem cuidado com offsets e replay.

Transactional Outbox também não substitui uma saga. Outbox publica de forma confiável o evento que inicia ou continua um fluxo; saga coordena várias transações locais e possíveis compensações.

Os padrões resolvem problemas relacionados, mas diferentes.

Quando usar Transactional Outbox

Use quando uma alteração confirmada no banco precisa inevitavelmente gerar uma mensagem e a perda dessa notificação criaria inconsistência. Pedidos, pagamentos, estoque, faturamento, cadastros e projeções de leitura são exemplos frequentes.

O padrão pode ser excessivo quando a notificação é apenas auxiliar e perder uma ocorrência é aceitável, quando tudo acontece no mesmo banco sem mensageria ou quando a plataforma já fornece uma garantia atômica equivalente.

Comece pelo requisito de consistência, não pela popularidade do nome.

Como implementar com segurança

  1. Defina o fato de negócio que merece ser publicado.
  2. Crie identidade estável e contrato versionado para o evento.
  3. Grave dados e outbox na mesma transação local.
  4. Escolha polling ou CDC conforme carga e capacidade operacional.
  5. Projete o relay para retentativa, concorrência e recuperação.
  6. Particione por agregado quando houver requisito de ordem.
  7. Torne consumidores idempotentes usando event_id.
  8. Defina quarentena, reprocessamento e política de limpeza.
  9. Instrumente atraso, backlog, erros e duplicidade.
  10. Teste quedas em cada fronteira antes de liberar o fluxo.

Nos testes, derrube a aplicação antes e depois do commit, interrompa o relay antes e depois da confirmação do broker, indisponibilize a mensageria e repita a entrega ao consumidor.

O resultado esperado não é ausência de falhas, mas recuperação sem perder o evento nem duplicar o efeito de negócio.

Erros comuns

  • Usar outra conexão para a outbox: as gravações deixam de ser atômicas.
  • Publicar dentro da transação do banco: mantém locks enquanto depende da rede e ainda não elimina todas as incertezas.
  • Apagar antes da confirmação: uma falha pode perder a mensagem.
  • Presumir entrega única: uma confirmação perdida pode produzir publicação duplicada.
  • Não versionar o payload: deploys passam a quebrar consumidores antigos.
  • Ignorar eventos presos: a tabela cresce enquanto o negócio fica inconsistente.
  • Consultar sem índice: o polling degrada conforme o volume aumenta.
  • Reter para sempre: outbox não deve virar arquivo histórico por acidente.

Perguntas frequentes

Transactional Outbox garante exactly once?

Não. O padrão evita perder a intenção de publicar, mas o relay pode enviar a mesma mensagem novamente. Consumidores idempotentes fazem o efeito parecer único dentro da fronteira definida.

O padrão precisa de Kafka?

Não. A outbox pode publicar para diferentes brokers ou serviços. Kafka é uma opção comum, especialmente com CDC, mas não faz parte da definição do padrão.

A tabela outbox substitui uma fila?

Não completamente. Ela mantém a intenção de publicar junto ao estado local. O broker continua oferecendo distribuição, retenção, grupos de consumidores e escala de mensageria.

Transactional Outbox é Event Sourcing?

Não. Na outbox, o estado atual continua sendo persistido normalmente e eventos são registrados para integração. Em Event Sourcing, a sequência de eventos é a fonte principal para reconstruir o estado.

É melhor implementar com polling ou CDC?

Depende da escala e da maturidade operacional. Polling é mais simples; CDC tende a oferecer menor latência e menos consultas, mas adiciona conectores e gestão de offsets.

Por quanto tempo os eventos devem ficar na outbox?

Até existir confirmação suficiente de publicação e passar a janela necessária para auditoria e recuperação. O prazo varia conforme risco, volume e requisitos regulatórios.

Qual é a relação entre Transactional Outbox e Saga?

A outbox publica com segurança os eventos de cada transação local. A saga usa eventos ou comandos para avançar um processo entre serviços e executar compensações quando necessário.

Conclusão

Transactional Outbox elimina a perigosa lacuna entre confirmar dados e registrar que uma mensagem precisa ser publicada.

A alteração de negócio e o evento entram na mesma transação local; um relay recuperável cuida da entrega assíncrona por polling ou CDC.

A confiabilidade depende do conjunto: identidade estável, consumidores idempotentes, ordenação quando necessária, retentativas controladas, limpeza e observabilidade.

Com essa base, uma indisponibilidade temporária deixa de causar perda silenciosa.

O próximo passo é entender como eventos confiáveis participam de transações distribuídas mais longas — exatamente o problema tratado pelo padrão Saga.