ArquiteturaAvançado10 min de leitura8 seções1987, Data aproximada

Saga

Coordena uma transação distribuída de longa duração como uma sequência de transações locais, cada uma com uma ação compensatória para desfazer semanticamente em caso de falha.

Em 10 segundos

Quando uma operação atravessa vários serviços com bancos próprios, não existe rollback global: a Saga divide o fluxo em transações locais e, se um passo falha, executa compensações que desfazem os anteriores no plano do negócio — atomicidade semântica com consistência eventual.

Antes de ter nome

O paper de 1987 tratava de transações longas num só banco; a releitura para microsserviços, com um passo por serviço, veio com a arquitetura distribuída dos anos 2010.

  • #transacao-distribuida
  • #microsservicos
  • #compensacao
  • #consistencia-eventual
Analogia

A reserva de viagem

Você monta uma viagem em três reservas: voo, hotel e carro. Cada uma é confirmada na hora, em empresas diferentes — não existe um 'botão único' que reserve tudo ou nada. Se a locadora não tem carro na data, você não deixa o resto pendurado: liga para o hotel e cancela, depois cancela o voo (pagando eventual taxa). Nada disso 'volta no tempo' — o hotel chegou a ter sua reserva por alguns minutos — mas, no fim, o mundo fica como se a viagem nunca tivesse sido marcada. É exatamente o que uma Saga faz: confirma passo a passo e, na falha, desfaz por compensação, na ordem inversa.

01Conceito

O problema

Uma operação de negócio em microsserviços atravessa vários bancos: reservar estoque, cobrar pagamento, agendar entrega. Cada serviço garante ACID só dentro de casa — não existe commit/rollback que abranja o conjunto.

O two-phase commit (2PC) promete atomicidade distribuída, mas trava recursos enquanto todos votam, cria um coordenador que vira gargalo e não sobrevive a fluxos longos ou a participantes externos (a operadora de cartão não entra na sua transação).

A raiz do problema é o teorema de que consistência forte distribuída custa disponibilidade e latência: para o 2PC funcionar, cada participante segura locks até o coordenador decidir — em fluxos de segundos ou minutos, isso significa linhas de banco travadas, throughput despencando e um ponto único cuja queda deixa todos em estado 'preparado' sem saber o desfecho. A Saga aceita o trade-off oposto: libera cada recurso imediatamente (commit local) e paga o preço em isolamento.

Esse preço tem nome: estados intermediários visíveis. Entre o passo 1 e a compensação, outro cliente pode ver o estoque reservado de um pedido que vai falhar. A Saga entrega atomicidade SEMÂNTICA (no fim, tudo aconteceu ou nada 'vale') e consistência eventual, mas não entrega o I de ACID — quem precisa esconder intermediários usa contramedidas como reservas explícitas, status 'pendente' e bloqueio semântico no modelo de negócio.

A escolha entre orquestração e coreografia define quem carrega essa complexidade. Orquestração: um componente central conhece a receita, comanda cada serviço e decide compensar — o fluxo é explícito, testável e observável, mas o orquestrador concentra acoplamento e precisa ser resiliente (persistir o estado da saga para retomar após crash). Coreografia: cada serviço reage a eventos e publica os seus ('PagamentoFalhou' faz o Estoque se auto-liberar) — não há ponto central, mas a receita fica espalhada, e responder 'em que passo esse pedido está?' exige rastreamento distribuído. Regra prática: poucos passos e times autônomos favorecem coreografia; fluxos longos com decisões condicionais favorecem orquestração.

Como se encaixa
  1. Pedido
  2. passo 1 okReservar estoque
  3. passo 2 falhaCobrar pagamento
  4. desfaz na ordem inversaCompensar: liberar estoque
  5. estado final consistenteSaga abortada
Cada passo confirma localmente; a falha no meio dispara as compensações dos passos já feitos, em ordem inversa.
02Anatomia

Navegue pelas peças

Camada 1/4Orquestrador / Coreografia

Camada 1

Orquestrador / Coreografia

quem conhece a receita e decide compensar

Na orquestração, um componente central persiste o estado da saga, envia comandos e reage a respostas — a receita mora em um lugar só. Na coreografia, a receita se distribui: cada serviço assina eventos e sabe apenas seu próximo passo. Em ambos, alguém precisa decidir quando o fluxo avança e quando reverte.

No código

await executarSaga([reservarEstoque, cobrarPagamento, agendarEntrega]);

Se violar: sem coordenação definida, cada falha vira decisão ad-hoc: passos ficam órfãos, meio-executados, e ninguém sabe se o pedido terminou ou travou.

03Estrutura

Estrutura

Diagrama de classes
04Implementação

Código

interface Passo {
  executar(): Promise<void>;
  compensar(): Promise<void>;
}

// Orquestrador simples: executa em ordem e compensa ao falhar
async function executarSaga(passos: Passo[]): Promise<void> {
  const feitos: Passo[] = [];
  try {
    for (const passo of passos) {
      await passo.executar();
      feitos.push(passo);
    }
  } catch (erro) {
    // compensa na ordem inversa dos passos concluídos
    for (const passo of feitos.reverse()) {
      await passo.compensar();
    }
    throw erro;
  }
}

const reservarEstoque: Passo = {
  executar: async () => { /* reserva */ },
  compensar: async () => { /* libera reserva */ },
};
05No mundo real

Casos de uso

Checkout de e-commerce distribuído

Cenário

No fechamento do pedido, quatro serviços entram em cena: Pedido, Estoque, Pagamento e Entrega — cada um com seu banco, e o Pagamento ainda depende da operadora de cartão.

Como o padrão entra

Uma saga orquestrada sequencia reservar estoque → cobrar → agendar entrega. Cartão recusado? O orquestrador dispara 'liberar reserva' e marca o pedido como falho — o cliente vê um erro limpo, não um pedido zumbi com estoque preso.

Trade-off: Entre a reserva e a cobrança, o item aparece indisponível para outros clientes mesmo que a compra vá falhar — janela de isolamento fraco que times de estoque precisam aceitar e monitorar.

Abertura de conta bancária

Cenário

Onboarding digital: verificar KYC no serviço de compliance, criar a conta no core bancário, emitir o cartão em um parceiro externo. O KYC pode levar minutos e o parceiro tem instabilidade conhecida.

Como o padrão entra

Saga de longa duração: cada etapa commita localmente e o estado persiste ('KYC aprovado, conta criada, cartão pendente'). Se a emissão do cartão falha em definitivo, compensa-se encerrando a conta recém-criada e notificando o cliente — sem contas fantasma no core.

Trade-off: Compensar 'conta criada' não é apagar: regulação exige trilha de auditoria, então a compensação é um encerramento formal com registro — mais caro de projetar que um simples delete.

Booking de viagem

Cenário

Uma plataforma vende o pacote voo + hotel + carro consumindo três fornecedores externos, cada um com API, prazos de cancelamento e políticas próprias.

Como o padrão entra

A saga reserva em sequência e guarda, por passo, o identificador de cancelamento do fornecedor. Sem carro disponível, cancela hotel e voo pelos códigos guardados — o cliente recebe 'pacote indisponível' em vez de um voo solto que ele não pediu.

Trade-off: Compensação com custo real: fornecedores cobram taxa de cancelamento ou têm janelas de gratuidade — a ordem dos passos vira decisão financeira (reserve por último o mais caro de desfazer).

A compensação que não é idempotente

Compila, roda, passa no code review. E quebra depois.

O caminho feliz foi pensado com cuidado. A compensação foi escrita depois, às pressas, e assume que roda exatamente uma vez — que é justamente o que um sistema distribuído não garante.

async function compensarReserva(pedidoId: string) {
  const reserva = await repo.buscar(pedidoId);
  // Devolve ao estoque. Sem verificar se ja devolveu.
  await estoque.incrementar(reserva.sku, reserva.quantidade);
}

async function compensarCobranca(pedidoId: string) {
  const cobranca = await repo.buscarCobranca(pedidoId);
  // Estorna. Sem chave de idempotencia.
  await gateway.estornar(cobranca.transacaoId, cobranca.valor);
}

// A saga executa a compensacao, o ACK se perde na rede,
// o orquestrador reexecuta por seguranca.
// Resultado: 2 unidades voltaram ao estoque onde so 1 saiu,
// e o cliente foi estornado duas vezes.

O que quebra

  • Com ACK perdidoO orquestrador reexecuta a compensação e o efeito é aplicado duas vezes — estoque inflado, estorno duplicado.
  • Na conciliaçãoO saldo diverge de um jeito que não bate com nenhum evento registrado, e ninguém consegue explicar de onde veio.
  • No incidenteReprocessar a fila de compensações para 'garantir' amplifica o dano em vez de corrigi-lo.

A saída: Compensação é o passo que mais precisa ser idempotente, porque ela roda justamente quando o sistema já está instável. Toda compensação carrega chave de idempotência e verifica o estado antes de agir — 'já compensei este pedido?' vem antes de 'compensar'.

07Cuidado

Armadilhas

  1. Compensação não-idempotente

    Mensageria com entrega at-least-once VAI reentregar comandos de compensação — e um estorno executado duas vezes devolve o dinheiro em dobro. Toda compensação precisa de chave de idempotência (estornar POR pedido, não 'estornar X reais') e de checagem de estado antes de agir: liberar uma reserva já liberada deve ser um no-op silencioso.

  2. Passo não-compensável no meio do fluxo

    Enviar o e-mail 'compra confirmada!' como passo 2 de 4 é irreversível: se o passo 3 falhar, não existe des-enviar. Ações sem compensação (e-mails, webhooks para terceiros, transferências liquidadas) devem ir para o fim da saga, depois do último ponto de falha possível — ou serem redesenhadas ('processando compra' antes, confirmação só no sucesso).

  3. Saga sem timeout

    Se o serviço de pagamento simplesmente não responde, a saga sem timeout fica 'executando' para sempre — estoque preso, pedido pendurado, cliente no limbo. Cada passo precisa de prazo máximo e de um desfecho padrão ao estourá-lo (normalmente compensar tudo). Cuidado com o clássico: o timeout dispara a compensação e a resposta atrasada chega depois — o desenho precisa decidir quem vence.

08Decisão

Quando usar × quando evitar

Use quando

4
  • Uma operação de negócio precisa de atomicidade entre vários serviços com bancos separados.
  • O fluxo é longo ou depende de sistemas externos, tornando 2PC inviável.
  • Você aceita consistência eventual e consegue definir compensações claras por passo.
  • Já usa arquitetura orientada a eventos ou CQRS e quer coordenar processos de negócio.

Evite quando

3
  • Tudo cabe em uma única transação ACID local — não há por que distribuir.
  • O domínio exige isolamento forte e não tolera estados intermediários visíveis.
  • Não é possível compensar passos de forma segura (efeitos irreversíveis sem contrapartida).

Testar o que ficou

3 armadilhas de Saga viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • Step Functions / TemporalOrquestradores de fluxo de trabalho encadeiam passos entre serviços e disparam compensações quando um deles falha no meio.
  • checkout de e-commerceReservar estoque, cobrar e despachar são passos em serviços distintos; se a cobrança falha, a reserva é desfeita por uma ação compensatória.
  • reserva de viagem (voo + hotel)Sem transação global entre companhias, cada reserva confirmada é cancelada por compensação quando a etapa seguinte não fecha.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Passos + compensações; sem 2PC entre serviços.
await reserva; try { await cobra; } catch { await libera; }

O que ele cobra

2 saltos a mais entre a chamada e o efeito.

  • Cada passo precisa de uma ação de compensação que desfaça o que ele fez
  • Existe uma janela em que o sistema está parcialmente aplicado, visível a quem observa

Não vale se: os passos cabem numa transação local do mesmo banco — aí uma transação ACID resolve sem inventar compensações.

Costuma ser confundido com

Onde isto aparece

Continue por aqui