Saga Pattern
Como lidar com transações distribuídas entre microsserviços usando coreografia ou orquestração com compensações.
Que problema resolve
Em um monólito, uma transação de banco de dados pode abranger múltiplas tabelas com um simples BEGIN → COMMIT → ROLLBACK. Mas em uma arquitetura de microsserviços, cada serviço tem seu próprio banco de dados. Não existe uma transação global capaz de fazer rollback em 5 bancos de dados diferentes se algo falhar no passo 3.
O Saga Pattern resolve esse problema dividindo uma transação distribuída em uma sequência de transações locais, onde cada passo tem uma ação compensatória que desfaz seu efeito caso um passo posterior falhe.
Exemplo do problema
Imagine um fluxo de compra em um e-commerce:
- Serviço de Pedidos: Criar pedido
- Serviço de Estoque: Reservar estoque
- Serviço de Pagamentos: Cobrar o cliente
- Serviço de Envios: Agendar envio
Se o pagamento falhar no passo 3, você precisa desfazer a reserva de estoque (passo 2) e cancelar o pedido (passo 1). Sem um padrão definido, isso se torna um caos de chamadas manuais e estados inconsistentes.
Por que não usar transações distribuídas (2PC)
O protocolo Two-Phase Commit (2PC) parece a solução óbvia, mas tem problemas sérios em microsserviços:
| Aspecto | 2PC | Saga |
|---|---|---|
| Disponibilidade | Todos os participantes devem estar disponíveis simultaneamente | Cada passo é independente |
| Latência | Bloqueios durante toda a transação | Cada passo libera recursos imediatamente |
| Escalabilidade | Os locks distribuídos limitam o throughput | Sem locks, cada serviço escala de forma independente |
| Acoplamento | Requer um coordenador que conheça a todos | Os serviços se comunicam por eventos |
| Recuperação | Se o coordenador falha, os participantes ficam bloqueados | As compensações são executadas de forma assíncrona |
Como funciona
Uma saga é uma sequência de transações locais onde:
- Cada transação atualiza dados dentro de um único serviço
- Cada transação publica um evento ou mensagem que dispara o próximo
- Se uma transação falha, executam-se compensações na ordem inversa
Fluxo bem-sucedido:
T1 → T2 → T3 → T4 ✅
Fluxo com falha em T3:
T1 → T2 → T3 ❌ → C2 → C1
(C = compensação)
Existem duas estratégias principais para coordenar uma saga:
Coreografia (Choreography)
Cada serviço escuta eventos e decide o que fazer. Não há um coordenador central.
┌──────────┐ PedidoCriado ┌──────────────┐
│ Pedidos │ ──────────────────► │ Estoque │
└──────────┘ └──────┬───────┘
│
EstoqueReservado
│
▼
┌──────────────┐
│ Pagamentos │
└──────┬───────┘
│
PagamentoAprovado
│
▼
┌──────────────┐
│ Envios │
└──────────────┘
Vantagens: Sem ponto único de falha, serviços desacoplados, fácil adicionar participantes. Desvantagens: Difícil entender o fluxo completo, risco de ciclos, difícil depurar.
Orquestração (Orchestration)
Um componente central coordena toda a saga. Ele sabe quais passos executar e quais compensações aplicar.
┌──────────────────┐
│ Orquestrador │
│ de Saga │
└──┬───┬───┬───┬──┘
│ │ │ │
Criar │ │ │ │ Agendar
pedido │ │ │ │ envio
▼ │ │ ▼
┌────────┐│ │┌────────┐
│Pedidos ││ ││Envios │
└────────┘│ │└────────┘
Reservar │ Cobrar
estoque│ │ pagamento
▼ ▼
┌────────┐┌────────┐
│Estoque ││Pagam. │
└────────┘└────────┘
Vantagens: Fluxo centralizado, controle explícito das compensações, fácil depurar. Desvantagens: Potencial ponto único de falha, risco de God Service, acoplamento.
Quando escolher cada estratégia
| Critério | Coreografia | Orquestração |
|---|---|---|
| Número de passos | 2-4 passos simples | 5+ passos ou fluxos complexos |
| Visibilidade | Aceitável se for simples | Necessária para fluxos críticos |
| Times | Autônomos e independentes | Time central gerencia o fluxo |
| Compensações | Simples e diretas | Complexas com lógica condicional |
| Monitoramento | Rastreabilidade distribuída | Dashboard centralizado |
Transações compensatórias
As compensações não são simplesmente um “undo”. São ações que semanticamente revertem o efeito de uma transação anterior:
| Transação | Compensação |
|---|---|
| Criar pedido | Cancelar pedido (mudar o estado, não excluir) |
| Reservar estoque | Liberar o estoque reservado |
| Cobrar pagamento | Emitir reembolso |
| Agendar envio | Cancelar o agendamento do envio |
Pontos-chave sobre compensações:
- Não são rollbacks: Não apagam dados, criam novos registros que revertem o efeito
- Devem ser idempotentes: Executá-las várias vezes produz o mesmo resultado
- Podem falhar: Você precisa de retentativas para compensações que falharam
- Mantêm auditoria: Tudo fica registrado (ação original e compensação)
Exemplo prático: Saga de compra com orquestração
Caminho feliz
Orquestrador Pedidos Estoque Pagamentos Envios
│ │ │ │ │
│── CriarPedido ───►│ │ │ │
│◄── PedidoCriado ──│ │ │ │
│ │ │ │ │
│── ReservarEstoque ────────────►│ │ │
│◄── EstoqueReservado ──────────│ │ │
│ │ │ │ │
│── CobrarPagamento ────────────────────────►│ │
│◄── PagamentoAprovado ─────────────────────│ │
│ │ │ │ │
│── AgendarEnvio ───────────────────────────────────────►│
│◄── EnvioAgendado ─────────────────────────────────────│
│ │ │ │ │
✅ Saga concluída
Falha no pagamento (com compensações)
Orquestrador Pedidos Estoque Pagamentos
│ │ │ │
│── CriarPedido ───►│ │ │
│◄── PedidoCriado ──│ │ │
│ │ │ │
│── ReservarEstoque ────────────►│ │
│◄── EstoqueReservado ──────────│ │
│ │ │ │
│── CobrarPagamento ────────────────────────►│
│◄── PagamentoRecusado ─────────────────────│ ❌
│ │ │ │
│ ┌── COMPENSAÇÕES ────────────────────┐ │
│ │── LiberarEstoque ──────────►│ │ │
│ │◄── EstoqueLiberado ────────│ │ │
│ │── CancelarPedido ──►│ │ │ │
│ │◄── PedidoCancelado ─│ │ │ │
│ └────────────────────────────────────┘ │
│ │ │
❌ Saga compensada
Estado persistido do orquestrador
{
"sagaId": "saga-12345",
"tipo": "CompraCompleta",
"estado": "COMPENSANDO",
"pasoActual": "LiberarStock",
"datos": {
"pedidoId": "ped-001",
"clienteId": "cli-789",
"monto": 150.00
},
"historial": [
{ "paso": "CrearPedido", "estado": "COMPLETADO" },
{ "paso": "ReservarStock", "estado": "COMPLETADO" },
{ "paso": "CobrarPago", "estado": "FALLIDO", "motivo": "Fondos insuficientes" },
{ "paso": "LiberarStock", "estado": "COMPENSANDO" }
]
}
Vantagens
- Consistência eventual: Garante que o sistema chegará a um estado consistente
- Autonomia dos serviços: Cada serviço mantém seu próprio banco de dados e transações locais
- Resiliência: Se um passo falha, as compensações restauram a consistência
- Escalabilidade: Não requer locks distribuídos nem coordenação síncrona
- Auditoria natural: Cada passo e compensação fica registrado como evento
- Flexibilidade: É possível adicionar ou modificar passos sem afetar todo o fluxo
Trade-offs / Desvantagens
- Complexidade: Projetar compensações corretas para cada passo não é trivial
- Consistência eventual: O sistema pode estar temporariamente inconsistente
- Difícil de depurar: Rastrear uma saga requer boas ferramentas de observabilidade
- Compensações imperfeitas: Nem sempre é possível reverter completamente uma ação
- Ordem de execução: Na coreografia, garantir a ordem correta é complexo
- Testing: Testar todos os caminhos possíveis exige esforço significativo
- Janela de inconsistência: Durante a execução, os dados podem estar em estado parcial
Quando usar
- Transações que cruzam múltiplos microsserviços com bancos de dados independentes
- Fluxos de negócio de longa duração (minutos, horas ou dias)
- Sistemas onde a consistência eventual é aceitável
- Quando você precisa de auditoria completa de cada passo do processo
- Fluxos com passos que podem falhar e precisam de compensação definida
- Sistemas event-driven onde os serviços já se comunicam por eventos
Quando evitar
- Operações que exigem consistência forte e imediata (ACID)
- Fluxos simples que envolvem apenas 2 serviços (pode ser overkill)
- Quando as compensações são impossíveis ou não fazem sentido
- Sistemas onde a janela de inconsistência não é aceitável
- Times sem experiência em sistemas distribuídos
- Quando um monólito com transações locais resolve o problema
Ferramentas e implementações comuns
| Categoria | Opções |
|---|---|
| Orquestradores | Temporal, Camunda, AWS Step Functions, Netflix Conductor |
| Mensageria | Apache Kafka, RabbitMQ, Amazon SQS/SNS, Azure Service Bus |
| Frameworks | Axon Framework (Java), MassTransit (.NET), Eventuate Tram |
| Observabilidade | Jaeger, Zipkin, OpenTelemetry (tracing distribuído) |
| Estado da saga | PostgreSQL, Redis, DynamoDB |
Relação com outros padrões
A saga raramente aparece sozinha. Ela se apoia em um ecossistema de padrões complementares:
┌─────────────────────────────────────────────────┐
│ Ecossistema da Saga │
│ │
│ ┌──────────┐ publica ┌──────────────┐ │
│ │ Saga │ ──────────────►│ Outbox │ │
│ │ (passos) │ eventos │ (confiável) │ │
│ └────┬─────┘ └──────────────┘ │
│ │ │
│ │ cada passo é │
│ │ idempotente │
│ ▼ │
│ ┌──────────────┐ protege ┌───────────┐ │
│ │ Idempotency │ ◄────────────►│ Circuit │ │
│ │ Pattern │ chamadas │ Breaker │ │
│ └──────────────┘ └───────────┘ │
│ │
│ ┌──────────┐ retentativas ┌───────────┐ │
│ │ Retry │ ◄────────────────►│ Timeout │ │
│ │ Pattern │ com limite │ Pattern │ │
│ └──────────┘ └───────────┘ │
└─────────────────────────────────────────────────┘
- Outbox Pattern: Garante que os eventos de cada passo sejam publicados de forma confiável
- Idempotency Pattern: Cada passo e compensação deve ser idempotente para retentativas seguras
- Circuit Breaker: Protege o orquestrador quando um serviço participante está fora do ar
- Retry Pattern: Retenta passos que falharam com backoff antes de acionar compensações
- Timeout Pattern: Define um limite de tempo para cada passo antes de considerá-lo falho
- Event-Driven Architecture: A saga é construída sobre um sistema de eventos
- DDD: Os bounded contexts definem os limites de cada participante
Erros comuns
| Erro | Consequência | Solução |
|---|---|---|
| Compensações não idempotentes | Duplicação de reembolsos | Usar IDs de idempotência |
| Não persistir o estado da saga | Sagas perdidas após reinício | Salvar o estado em BD durável |
| Ignorar falhas nas compensações | Estado inconsistente permanente | Retentativas e dead-letter queue |
| Saga com passos demais | Complexidade ingerenciável | Dividir em sub-sagas |
| Não monitorar sagas ativas | Sagas travadas sem detecção | Dashboard com alertas |
Próximos passos
Uma vez que você domina a saga, explore o Idempotency Pattern para garantir que os passos da saga sejam seguros diante de retentativas.