Saga pattern: transações distribuídas

1. Introdução ao Problema das Transações Distribuídas

1.1. Desafios de consistência em microsserviços

Em arquiteturas monolíticas, uma única transação de banco de dados garante atomicidade, consistência, isolamento e durabilidade (ACID). Quando migramos para microsserviços, cada serviço possui seu próprio banco de dados, rompendo a capacidade de realizar transações distribuídas tradicionais. Uma operação de negócio — como criar um pedido, reservar estoque e processar pagamento — agora atravessa múltiplos serviços, cada um com seu estado independente.

1.2. Limitações do modelo ACID em ambientes distribuídos

O Two-Phase Commit (2PC) foi a tentativa clássica de estender ACID para sistemas distribuídos, mas apresenta problemas severos: bloqueio de recursos durante a fase de preparação, ponto único de falha no coordenador, e baixa escalabilidade. Em sistemas de alta disponibilidade, o 2PC se torna inviável. É nesse contexto que surge o padrão Saga.

1.3. O que é uma Saga: conceito fundamental e origem

O termo "Saga" foi introduzido por Hector Garcia-Molina e Kenneth Salem em 1987. Uma Saga é uma sequência de transações locais onde cada transação publica um evento que dispara a próxima transação. Se uma transação falha, a Saga executa transações compensatórias para desfazer o que já foi feito. Não há atomicidade global, mas sim consistência eventual.

Exemplo conceitual de Saga:
Passo 1: Serviço de Pedidos cria pedido (status: pendente)
Passo 2: Serviço de Estoque reserva itens
Passo 3: Serviço de Pagamento processa cobrança
Passo 4: Serviço de Pedidos confirma pedido (status: confirmado)

Falha no Passo 3:
- Compensação Passo 2: Serviço de Estoque libera reserva
- Compensação Passo 1: Serviço de Pedidos cancela pedido (status: cancelado)

2. Coreografia vs Orquestração: Dois Modelos de Saga

2.1. Coreografia: eventos descentralizados e acoplamento fraco

Na coreografia, cada serviço reage a eventos emitidos por outros serviços. Não há coordenador central. Os serviços se comunicam exclusivamente via message broker.

Exemplo de coreografia (fluxo textual):
1. Serviço de Pedidos publica evento "PedidoCriado"
2. Serviço de Estoque consome "PedidoCriado", reserva itens, publica "EstoqueReservado"
3. Serviço de Pagamento consome "EstoqueReservado", processa pagamento, publica "PagamentoAprovado"
4. Serviço de Pedidos consome "PagamentoAprovado", atualiza pedido para confirmado

Vantagens: acoplamento fraco, escalabilidade horizontal, simplicidade inicial. Desvantagens: difícil rastrear fluxos complexos, lógica de negócio espalhada, risco de loops de eventos.

2.2. Orquestração: um coordenador central (Saga Orchestrator)

Na orquestração, um serviço dedicado (orquestrador) gerencia o fluxo da Saga, chamando cada serviço via comando e tratando respostas.

Exemplo de orquestração (fluxo textual):
1. Orquestrador chama Serviço de Pedidos: "criarPedido()" → resposta: "pedidoId=123"
2. Orquestrador chama Serviço de Estoque: "reservarEstoque(pedidoId=123)" → resposta: "ok"
3. Orquestrador chama Serviço de Pagamento: "processarPagamento(pedidoId=123)" → resposta: "falha"
4. Orquestrador chama Serviço de Estoque: "liberarEstoque(pedidoId=123)" → compensação
5. Orquestrador chama Serviço de Pedidos: "cancelarPedido(pedidoId=123)" → compensação

Vantagens: visibilidade total do fluxo, tratamento centralizado de falhas, lógica de negócio concentrada. Desvantagens: ponto único de falha (orquestrador), maior acoplamento, complexidade de implementação.

2.3. Trade-offs: escalabilidade, visibilidade e complexidade operacional

Aspecto Coreografia Orquestração
Acoplamento Baixo Médio
Visibilidade Baixa Alta
Escalabilidade Alta Limitada pelo orquestrador
Complexidade Difícil de debugar Fácil de debugar
Performance Menor latência Maior latência

3. Estrutura de uma Transação Saga

3.1. Transações locais e compensações: a base do padrão

Cada passo de uma Saga deve ter uma transação compensatória correspondente. A transação compensatória desfaz semanticamente o que foi feito, não necessariamente restaurando o estado anterior (ex: enviar e-mail de cancelamento não apaga o e-mail original, mas compensa o efeito).

3.2. Fluxo de sucesso: encadeamento de passos

Fluxo de sucesso em orquestração (texto):
1. SagaInicio → criarPedido(pedidoId=1, itens=[A,B])
2. SagaPasso → reservarEstoque(pedidoId=1, itens=[A,B])
3. SagaPasso → processarPagamento(pedidoId=1, valor=150.00)
4. SagaPasso → confirmarPedido(pedidoId=1)
5. SagaFim → pedidoId=1 concluído com sucesso

3.3. Fluxo de falha: ativação de transações compensatórias

Fluxo de falha em orquestração (texto):
1. SagaInicio → criarPedido(pedidoId=2, itens=[C])
2. SagaPasso → reservarEstoque(pedidoId=2, itens=[C]) → ok
3. SagaPasso → processarPagamento(pedidoId=2, valor=75.00) → FALHA (saldo insuficiente)
4. SagaCompensacao → liberarEstoque(pedidoId=2, itens=[C])
5. SagaCompensacao → cancelarPedido(pedidoId=2)
6. SagaFim → pedidoId=2 cancelado, recursos liberados

4. Garantias de Consistência e Isolamento

4.1. Consistência eventual vs consistência forte

Sagas não oferecem consistência forte. Durante a execução, o sistema pode ficar em estado intermediário inconsistente. A consistência é alcançada apenas após a conclusão bem-sucedida de todos os passos ou após a execução completa das compensações.

4.2. Níveis de isolamento em Sagas: ACD (sem Atomicidade tradicional)

Sagas oferecem ACD: Atomicidade (através de compensações), Consistência (eventual) e Durabilidade. O isolamento tradicional (I do ACID) não é garantido. Leituras sujas podem ocorrer durante a execução da Saga.

4.3. Estratégias para lidar com leituras sujas e anomalias

  • Semântica de contrapartida: usar contadores ou estados provisórios
  • Log de compensação: registrar cada ação para permitir rollback
  • Leituras apenas de Sagas concluídas: expor dados apenas quando a Saga termina
  • Bloqueio otimista: usar versões e timestamps para detectar conflitos

5. Implementação com Mensageria e Eventos

5.1. Uso de message brokers (RabbitMQ, Kafka) como espinha dorsal

Tanto coreografia quanto orquestração se beneficiam de message brokers. RabbitMQ oferece filas confiáveis com confirmação de entrega. Kafka oferece logs imutáveis e replay de eventos, ideal para auditoria.

5.2. Garantia de entrega e ordenação de mensagens

Configuração de garantia de entrega (texto):
- Produtor: confirmação de entrega (ack) habilitada
- Consumidor: processamento idempotente, confirmação manual (manual ack)
- Fila: dead letter queue para mensagens com falha repetida
- Ordenação: partição única (Kafka) ou fila FIFO (RabbitMQ)

5.3. Tratamento de duplicatas e idempotência nas ações

Cada ação deve ser idempotente: executar a mesma operação múltiplas vezes produz o mesmo resultado.

Exemplo de idempotência (texto):
- "reservarEstoque(pedidoId=3, itens=[A])" verifica se já existe reserva para pedidoId=3
- Se existe: retorna sucesso sem duplicar
- Se não existe: cria reserva e retorna sucesso

6. Monitoramento, Recuperação e Tolerância a Falhas

6.1. Logging de estado da Saga (Saga Log)

Cada passo da Saga deve ser registrado em um log persistente com status: INICIADO, SUCESSO, FALHA, COMPENSADO.

Exemplo de Saga Log (texto):
sagaId=abc123 | passo=1 | servico=pedidos | acao=criarPedido | status=SUCESSO | timestamp=2024-01-01T10:00:00Z
sagaId=abc123 | passo=2 | servico=estoque | acao=reservarEstoque | status=SUCESSO | timestamp=2024-01-01T10:00:01Z
sagaId=abc123 | passo=3 | servico=pgto | acao=processarPagamento | status=FALHA | timestamp=2024-01-01T10:00:02Z
sagaId=abc123 | passo=2c | servico=estoque | acao=liberarEstoque | status=SUCESSO | timestamp=2024-01-01T10:00:03Z
sagaId=abc123 | passo=1c | servico=pedidos | acao=cancelarPedido | status=SUCESSO | timestamp=2024-01-01T10:00:04Z

6.2. Estratégias de rollback parcial e retry

Para falhas transitórias, implementar retry com backoff exponencial. Para falhas permanentes, ativar compensações.

6.3. Sagas pendentes: detecção e resolução assíncrona

Um worker de recuperação deve periodicamente verificar Sagas com status "pendente" há mais de um tempo limite e tentar concluí-las ou compensá-las.

7. Comparação com Alternativas e Boas Práticas

7.1. Quando usar Saga vs Two-Phase Commit (2PC)

Critério Saga 2PC
Consistência Eventual Forte
Disponibilidade Alta Baixa (bloqueio)
Latência Baixa Alta
Complexidade Média Alta
Escalabilidade Alta Baixa

Use Saga quando precisar de alta disponibilidade e escalabilidade. Use 2PC apenas em cenários críticos com baixa concorrência.

7.2. Armadilhas comuns: timeouts, deadlocks e falta de compensação

  • Timeouts: definir timeouts realistas para cada passo
  • Deadlocks: evitar Sagas circulares ou dependências cíclicas
  • Falta de compensação: toda ação deve ter uma compensação implementada
  • Compensação não reversível: algumas ações (enviar e-mail) não podem ser desfeitas; planejar compensação semântica

7.3. Padrões complementares: BFF, API Gateway e Event Sourcing

  • API Gateway: ponto único de entrada, pode iniciar Sagas
  • BFF (Backend for Frontend): adapta Sagas para diferentes clientes
  • Event Sourcing: armazena eventos como fonte de verdade, complementa Sagas com auditoria completa

8. Conclusão e Cenários de Uso Recomendados

8.1. Resumo dos prós e contras do padrão Saga

Prós: escalabilidade, alta disponibilidade, resiliência, modelagem natural para microsserviços.

Contras: consistência eventual, complexidade de compensações, necessidade de idempotência, monitoramento adicional.

8.2. Exemplos reais: e-commerce, reservas de viagem, sistemas financeiros

  • E-commerce: criar pedido, reservar estoque, processar pagamento, enviar notificação
  • Reservas de viagem: reservar voo, reservar hotel, alugar carro, processar pagamento
  • Sistemas financeiros: transferência entre contas em bancos diferentes, com compensações em caso de falha

8.3. Checklist para adoção em projetos de microsserviços

  1. Cada serviço possui banco de dados próprio?
  2. As operações de negócio atravessam múltiplos serviços?
  3. A consistência eventual é aceitável?
  4. É possível implementar compensações para cada ação?
  5. As operações são idempotentes?
  6. Existe monitoramento para Sagas pendentes?
  7. A equipe tem maturidade para lidar com complexidade adicional?

Se respondeu "sim" para a maioria, o padrão Saga é adequado.

Referências