Outbox Pattern
Visão Geral
O Outbox Pattern garante que Domain Events sejam publicados no Kafka/Redpanda de forma confiável, mesmo em caso de falhas na infraestrutura de mensageria.
Problema que resolve: Garantir que eventos gerados por um Aggregate sejam sempre publicados, evitando inconsistências entre o banco de dados e o barramento de eventos.
Implementação: Eventos são salvos em uma tabela outbox_messages na mesma transação do banco de dados. Um background service processa e publica esses eventos no Kafka com retry automático.
Conceitos Fundamentais
O Problema
Cenário sem Outbox:
1. Aggregate salva dados no banco ✅
2. Tenta publicar evento no Kafka ❌ (Kafka fora do ar)
3. Evento nunca é publicado
4. Workers nunca processam → Cliente não recebe notificação
Resultado: Inconsistência entre estado do banco e eventos publicados.
A Solução
Cenário com Outbox:
1. Aggregate salva dados no banco ✅
2. Evento salvo em tabela Outbox ✅ (mesma transação!)
3. Commit da transação ✅
4. Background worker lê Outbox periodicamente
5. Publica eventos no Kafka com retry automático
6. Evento marcado como processado
Resultado: Garantia de que todo evento será publicado eventualmente.
Garantias
- ✅ At-Least-Once Delivery: Evento será publicado pelo menos uma vez
- ✅ Atomicidade: Dados e eventos salvos na mesma transação
- ✅ Resiliência: Falhas no Kafka não impedem a operação
- ✅ Auditoria: Histórico completo de todos os eventos
Implementação no Santa Mão
Fluxo Completo
1. Aggregate gera evento
public class Order : BaseAggregateRoot
{
public void Create(Guid userId, Guid requestId)
{
// Lógica de negócio
Id = Guid.NewGuid();
Status = OrderStatus.Pending;
// Adicionar evento
AddDomainEvent(new OrderCreatedEvent(Id, userId, requestId));
}
}
2. Evento automaticamente salvo no Outbox
// EventsInterceptor captura eventos durante SaveChanges
// e salva na tabela outbox_messages na MESMA transação
await _unitOfWork.CommitAsync(cancellationToken);
3. Background worker processa
// OutboxProcessorService roda a cada 30 segundos
// 1. Busca eventos pendentes
// 2. Publica no Redpanda via MassTransit
// 3. Marca como processado
Estrutura da Tabela Outbox
CREATE TABLE outbox_messages (
id UUID PRIMARY KEY,
event_type VARCHAR(500) NOT NULL,
topic VARCHAR(500) NOT NULL,
payload JSONB NOT NULL,
headers JSONB NOT NULL,
status INTEGER NOT NULL, -- 0=Pending, 1=Processed, 2=Failed
created_at TIMESTAMP NOT NULL,
processed_at TIMESTAMP NULL,
retry_count INTEGER DEFAULT 0,
error_message VARCHAR(2000) NULL,
locked_by VARCHAR(100) NULL, -- Para distribuição
locked_until TIMESTAMP NULL,
partition_key INTEGER NOT NULL
);
Configuração
appsettings.json:
{
"OutboxConfiguration": {
"Enabled": true,
"ProcessingIntervalSeconds": 30,
"BatchSize": 100,
"MaxRetries": 3,
"RetentionDays": 7,
"MaxDegreeOfParallelism": 4,
"EnableDistributedProcessing": true
}
}
Parâmetros principais:
| Parâmetro | Descrição | Padrão |
|---|---|---|
ProcessingIntervalSeconds |
Intervalo entre processamentos | 30s |
BatchSize |
Eventos processados por vez | 100 |
MaxRetries |
Tentativas antes de marcar como falha | 3 |
RetentionDays |
Dias para manter eventos processados | 7 |
MaxDegreeOfParallelism |
Processamento paralelo | 4 |
Como Usar no Código
Passo 1: Criar o evento
public class OrderCreatedEvent : BaseEvent
{
public OrderCreatedEvent(Guid orderId, Guid userId, Guid requestId)
: base(Guid.NewGuid())
{
OrderId = orderId;
UserId = userId;
RequestId = requestId;
}
public Guid OrderId { get; }
public Guid UserId { get; }
public Guid RequestId { get; }
public override string GetTopic() => "santa-mao.order.created";
}
Passo 2: Adicionar evento no Aggregate
public class Order : BaseAggregateRoot
{
public void Create(Guid userId, Guid requestId)
{
Id = Guid.NewGuid();
UserId = userId;
Status = OrderStatus.Pending;
// Evento será automaticamente salvo no Outbox
AddDomainEvent(new OrderCreatedEvent(Id, userId, requestId));
}
}
Passo 3: Salvar (Outbox automático)
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand>
{
public async Task Handle(CreateOrderCommand request, CancellationToken ct)
{
var order = Order.Create(request.UserId, request.RequestId);
await _orderRepository.AddAsync(order, ct);
// Salva Order + OutboxMessages na MESMA transação
await _unitOfWork.CommitAsync(ct);
// Pronto! Evento será publicado automaticamente
}
}
Benefícios
✅ Confiabilidade - Eventos nunca são perdidos
✅ Consistência - Dados e eventos na mesma transação
✅ Resiliência - Sistema continua funcionando mesmo com Kafka offline
✅ Auditoria - Histórico completo de eventos
✅ Transparência - Desenvolvedores só precisam adicionar eventos no Aggregate
✅ Retry Automático - Sistema retenta automaticamente eventos falhados
Quando Usar e Quando NÃO Usar
✅ Usar Outbox Pattern Quando:
- Eventos precisam ser publicados com garantia de entrega
- Consistência entre banco e eventos é crítica
- Sistema distribuído com múltiplos consumers
- Auditoria de eventos é necessária
❌ NÃO Usar Outbox Pattern Quando:
- Sistema simples sem eventos críticos
- Perda ocasional de eventos é aceitável
- Overhead de processamento assíncrono não é aceitável
Relacionamento com Outros Padrões
Outbox Pattern trabalha em conjunto com:
- DDD - Captura Domain Events dos Aggregates
- CQRS - Garante que Commands publiquem eventos de forma confiável
- EDA - Fornece garantia de entrega para arquitetura orientada a eventos
Fluxo Integrado:
Aggregate Root (DDD) → Domain Event → Outbox Table → Background Worker → Redpanda (EDA) → Workers
Troubleshooting
Problema: Eventos não sendo processados
- Verificar se
Enabledestátrueno appsettings - Verificar logs do
OutboxProcessorService - Verificar conexão com Kafka
Problema: Muitos eventos falhados
- Ver
error_messagena tabela - Verificar conectividade com Kafka
- Reprocessar manualmente se necessário
Conclusão
O Outbox Pattern no Santa Mão garante que nenhum evento crítico seja perdido, mesmo em cenários de falha.
Implementação:
✅ Transparente para desenvolvedores
✅ Processamento automático em background
✅ Retry automático em caso de falhas
✅ Auditoria completa de eventos
A solução balanceia simplicidade de uso com robustez operacional, permitindo que o time foque em regras de negócio enquanto a infraestrutura garante a entrega confiável dos eventos.
Última atualização: Novembro 2025
Maintainers: Equipe de Arquitetura Santa Mão