O Desafio das Transações em Arquiteturas Distribuídas (SAGA)
A ideia deste post nasceu de um problema real que enfrentei, estudei como implementar, como tive exito na tarefa, resolvi escrever o post para fixar mais o aprendizado justamente para ter um material guia para o meu 'EU' do futuro geralmente eu faço isso no particular em meus cadernos e anotações, mas hoje resolvi deixar publico, algumas partes do texto as seguir foi formatada com IA, boa leitura e bom divertimento:
Imagine que você está desenvolvendo o backend de um e-commerce. Em uma arquitetura de microsserviços, o processo de finalização de uma compra não é uma única operação, mas uma série de etapas coordenadas entre serviços independentes. Por exemplo, um pedido pode envolver:
- Serviço de Pedidos: Cria um novo pedido com o status "pendente".
- Serviço de Pagamentos: Processa o pagamento do cliente.
- Serviço de Inventário: Deduz os itens comprados do estoque.
Em um mundo ideal, todas essas etapas ocorrem sem falhas. Mas o que acontece se o serviço de pagamentos aprova a transação, mas o serviço de inventário descobre que o último item do estoque estava, na verdade, danificado e não pode ser enviado?
Nesse ponto, temos um problema sério de inconsistência de dados. O cliente foi cobrado por um produto que não receberá. Precisamos de uma forma de "desfazer" o pagamento que já foi processado. Em um sistema monolítico com um único banco de dados, poderíamos simplesmente usar uma transação ACID (Atomicidade, Consistência, Isolamento, Durabilidade) e reverter tudo com um ROLLBACK. No entanto, em um ambiente de microsserviços, cada serviço gerencia seu próprio banco de dados. Não existe um ROLLBACK mágico que abranja múltiplos bancos de dados e serviços.
Essa é a essência do problema: como garantir a consistência dos dados em operações que se estendem por múltiplos serviços independentes?
Pense nisso como planejar uma viagem de férias que envolve três reservas separadas: um voo, um hotel e um carro alugado. Você reserva o voo com sucesso. Em seguida, tenta reservar o hotel, mas descobre que não há quartos disponíveis para as datas desejadas. Você não pode simplesmente continuar com a viagem. É preciso cancelar o voo que já foi reservado para evitar ser cobrado por algo que não usará. Cada reserva é uma transação local, e o cancelamento do voo é a "ação compensatória" necessária para reverter o processo.
A falha em gerenciar essa sequência de operações e suas possíveis reversões pode levar a estados de dados inconsistentes, resultando em prejuízos financeiros, problemas de estoque e, o mais importante, clientes insatisfeitos. É exatamente para resolver esse complexo quebra-cabeça de transações distribuídas que o padrão SAGA foi criado.
Este código não implementa o padrão SAGA intencionalmente. O objetivo aqui é demonstrar claramente o problema da inconsistência em uma arquitetura distribuída, preparando o terreno para a solução SAGA que virá a seguir.
Como Testar o Problema
Depois de executar o código com uvicorn main:app --reload, você pode usar uma ferramenta como o Postman, Insomnia ou curl para testar os cenários:
- Cenário de Sucesso:
POST http://127.0.0.1:8000/orders
Body (JSON): {"item_id": "item_123", "quantity": 1, "customer_id": "cliente_feliz"}
Resultado: O pedido será concluído, o pagamento aprovado e o estoque deduzido. O sistema permanece consistente.
- Cenário de Falha no Pagamento:
POST http://127.0.0.1:8000/orders
Body (JSON): {"item_id": "item_123", "quantity": 1, "customer_id": "cliente_sem_saldo"}
Resultado: O pagamento será recusado. O pedido será marcado como FALHA_PAGAMENTO. Este cenário é "seguro", pois nenhuma transação financeira ou de estoque foi efetivada.
- Cenário de Inconsistência (O Problema Real):
POST http://127.0.0.1:8000/orders
Body (JSON): {"item_id": "item_123", "quantity": 10, "customer_id": "cliente_com_saldo"} (quantidade maior que o estoque)
Resultado:
O serviço de Pedidos cria o pedido como PENDENTE.
O serviço de Pagamentos aprova o pagamento (pois o customer_id não contém "sem_saldo").
O serviço de Inventário falha, pois a quantidade (10) é maior que o estoque (5).
O serviço de Pedidos captura o erro e retorna uma mensagem de INCONSISTÊNCIA DE DADOS.
O estado final é inconsistente: o pagamento foi registrado em db_payments, mas o pedido está marcado como falho em db_orders e o estoque não foi alterado. O cliente foi cobrado por nada.
Este exemplo prático expõe a fragilidade de uma coreografia simples de chamadas diretas entre serviços. A falha em uma etapa tardia do processo, após outras etapas já terem confirmado suas transações locais, deixa o sistema em um estado corrompido. É precisamente para gerenciar essa complexidade, garantindo que seja possível reverter transações anteriores, que introduziremos o orquestrador SAGA.
Com o orquestrador:
O orquestrador é o componente central que conhece todas as etapas do processo. Ele chama cada serviço, aguarda a resposta e, se algo falhar, é sua responsabilidade invocar as ações compensatórias para reverter as operações que já foram concluídas com sucesso.
O Código com o Padrão SAGA (Orquestração)
As principais mudanças são:
Novos Endpoints de Compensação: Criamos endpoints como /payments/refund e /inventory/restore que desfazem as ações originais.
Lógica no Orquestrador: O endpoint /orders/saga agora contém a lógica para executar a sequência de etapas e, crucialmente, a lógica para executar as compensações em ordem inversa em caso de falha.
Como Testar a Solução SAGA
- Cenário de Sucesso:
POST http://127.0.0.1:8000/orders/saga
Body (JSON): {"item_id": "item_123", "quantity": 2, "customer_id": "cliente_com_saldo"}
Resultado no Console:
🚀 SAGA INICIADA para o pedido order_...
-> Executando etapa: Pagamento...
✅ PAGAMENTO: Pagamento para o pedido order_... APROVADO.
-> Executando etapa: Inventário...
✅ INVENTÁRIO: Estoque do item item_123 deduzido para 3.
✅ SAGA CONCLUÍDA com sucesso para o pedido order_...!
Estado Final: O sistema está consistente. O pagamento foi aprovado e o estoque deduzido.
- Cenário de Falha e Rollback (O Teste Real):
POST http://127.0.0.1:8000/orders/saga
Body (JSON): {"item_id": "item_123", "quantity": 10, "customer_id": "cliente_com_saldo"} (quantidade maior que o estoque)
Resultado no Console:
🚀 SAGA INICIADA para o pedido order_...
-> Executando etapa: Pagamento...
✅ PAGAMENTO: Pagamento para o pedido order_... APROVADO.
-> Executando etapa: Inventário...
❌ FALHA na etapa! Detalhe: {"detail":"Estoque insuficiente ou item não encontrado."}
⏪ INICIANDO ROLLBACK para o pedido order_...
-> Revertendo etapa via /payments/refund...
⏪ PAGAMENTO: Pagamento para o pedido order_... ESTORNADO.
Estado Final: O sistema permanece consistente. Embora a transação tenha falhado, a ação de compensação (estorno do pagamento) foi executada, garantindo que o cliente não fosse cobrado indevidamente. O estado final é como se a transação nunca tivesse ocorrido.
Este exemplo demonstra o poder do padrão SAGA de orquestração. Ele introduz uma complexidade maior no serviço coordenador, mas em troca oferece uma garantia robusta de consistência de dados em toda a arquitetura, tratando falhas de forma elegante e previsível.
Em suma, o padrão SAGA, especialmente no modelo de orquestração, emerge como uma solução elegante e pragmática para o desafio da consistência de dados em arquiteturas de microsserviços. Ao invés de depender de transações distribuídas frágeis, ele abraça a eventualidade de falhas, garantindo que para cada passo bem-sucedido, exista uma ação compensatória correspondente capaz de reverter o processo e manter o sistema íntegro. A implementação de um orquestrador, como demonstramos com FastAPI, centraliza a lógica da transação de negócio, adicionando uma camada de complexidade que é amplamente compensada pela resiliência, previsibilidade e pela confiança que traz ao sistema. Adotar o padrão SAGA não é apenas sobre programar para o "caminho feliz", mas sim projetar sistemas que se comportam de maneira segura e consistente diante do inevitável: a falha. Para qualquer desenvolvedor que projeta sistemas distribuídos complexos, dominar essa técnica é um passo fundamental na construção de aplicações verdadeiramente robustas e confiáveis.