Introdução: A Dor da Inconsistência

No mundo dos microserviços, cada serviço tem seu próprio banco de dados, sua própria fonte da verdade. Essa autonomia é fantástica para a escalabilidade e o desacoplamento, mas nos apresenta um desafio monumental: como garantir a consistência de um processo de negócio que se estende por vários serviços?

Imagine um fluxo de e-commerce simples:

  1. O Serviço de Pedidos cria um novo pedido.
  2. O Serviço de Pagamentos processa o cartão de crédito.
  3. O Serviço de Estoque debita o produto do inventário.

Se o pedido é criado e o pagamento é aprovado, mas o Serviço de Estoque falha (talvez o item tenha acabado no último segundo), o que acontece? O cliente foi cobrado por um produto que não receberá. O estado geral do sistema ficou inconsistente. Tentar resolver isso com chamadas HTTP diretas e blocos try-catch complexos é o caminho para um código frágil e um pesadelo de manutenção.

É para curar essa dor que o Padrão Saga existe. Ele nos fornece um modelo para gerenciar a consistência em transações de longa duração, garantindo que um processo de negócio ou conclua com sucesso, ou seja totalmente desfeito de forma limpa e auditável.

O que é o Padrão Saga?

Uma Saga é uma sequência de transações locais orquestradas para executar um trabalho maior. A regra de ouro é: para cada ação executada, deve haver uma ação de compensação correspondente, que é responsável por desfazer a ação original.

Pense na analogia de planejar uma viagem:

  1. Ação 1: Reservar um voo. (Compensação: Cancelar a reserva do voo).
  2. Ação 2: Reservar um hotel. (Compensação: Cancelar a reserva do hotel).
  3. Ação 3: Alugar um carro. (Compensação: Cancelar o aluguel do carro).

Se a reserva do voo e do hotel forem bem-sucedidas, mas o aluguel do carro falhar, a saga não para. Ela inicia o processo de compensação em ordem inversa: cancela a reserva do hotel e, em seguida, cancela a reserva do voo. Ao final, o sistema volta a um estado consistente — nenhuma viagem agendada, nenhum dinheiro gasto.

Orquestração vs. Coreografia

Existem duas formas principais de implementar uma Saga:

  1. Orquestração (a que veremos aqui): Um orquestador central (o “maestro”) é responsável por dizer a cada serviço o que fazer. Se uma etapa falha, o orquestrador comanda as ações de compensação. É explícito e mais fácil de depurar. O MassTransit brilha nesse modelo com suas Máquinas de Estado (State Machines).
  2. Coreografia: Não há um orquestrador central. Cada serviço publica eventos, e os outros serviços reagem a eles. É mais desacoplado, mas o fluxo geral pode ser difícil de rastrear.

Mão na Massa: Saga de Pedidos com .NET 9 e MassTransit

Vamos construir uma Saga de Orquestração simples para o nosso fluxo de e-commerce. Usaremos o MassTransit, um framework de Service Bus para .NET que torna a implementação de Sagas incrivelmente elegante.

Passo 1: Definindo os Contratos (As Mensagens)

Em um projeto compartilhado, definimos os comandos e eventos que conduzirão nossa saga. O CorrelationId é a chave que liga todas as mensagens de um mesmo processo.


public record SubmitOrder(Guid CorrelationId, string Item, int Quantity);

public record OrderSubmissionAccepted(Guid CorrelationId);
public record OrderSubmissionFaulted(Guid CorrelationId, string Reason);

public record ProcessPayment(Guid CorrelationId, decimal Amount);
public record RefundPayment(Guid CorrelationId);

Passo 2: O Cérebro - A Máquina de Estados da Saga

Este é o nosso orquestrador. Ele define os estados (Submetido, Aceito, Falha) e as transições.

Primeiro, a classe que guardará o estado de cada saga em andamento:


using MassTransit;

public class OrderState : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; }
    public string CurrentState { get; set; }
    public string? Item { get; set; }
    public int? Quantity { get; set; }
}

Agora, a Máquina de Estados que define o fluxo:

using MassTransit;
using My.Commerce.Contracts;

public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
    public State Submitted { get; private set; }
    public State Accepted { get; private set; }
    public State Faulted { get; private set; }

    public Event<SubmitOrder> SubmitOrder { get; private set; }
    public Event<OrderSubmissionAccepted> OrderAccepted { get; private set; }
    public Event<OrderSubmissionFaulted> OrderFaulted { get; private set; }

    public OrderStateMachine()
    {
        // 2. Mapeia a propriedade que armazena o estado atual
        InstanceState(x => x.CurrentState);

        // 3. Define o fluxo da Saga
        Initially(
            // O processo começa quando o evento SubmitOrder é recebido
            When(SubmitOrder)
                .Then(context => // Armazena dados do evento no estado da saga
                {
                    context.Saga.Item = context.Message.Item;
                    context.Saga.Quantity = context.Message.Quantity;
                })
                .SendCommand(context => // Envia um comando para processar o pagamento
                    new ProcessPayment(context.Message.CorrelationId, 100.0m)) // Valor fixo para o exemplo
                .TransitionTo(Submitted) // Muda o estado para "Submitted"
        );
        
        During(Submitted,
            // CENÁRIO FELIZ: O que fazer quando o evento de sucesso chega
            When(OrderAccepted)
                .TransitionTo(Accepted) // Muda o estado para "Aceito"
                .Finalize(), // Finaliza a saga com sucesso

            // CENÁRIO DE FALHA: O que fazer quando o evento de falha chega
            When(OrderFaulted)
                .Then(context => Console.WriteLine($"Pedido {context.Message.CorrelationId} falhou: {context.Message.Reason}"))
                // Envia o comando de COMPENSAÇÃO para estornar o pagamento
                .SendCommand(context => new RefundPayment(context.Message.CorrelationId))
                .TransitionTo(Faulted) // Muda o estado para "Falha"
                .Finalize() // Finaliza a saga
        );
        
        // Remove a instância da saga do banco de dados quando ela é finalizada.
        SetCompletedWhenFinalized();
    }
}

Passo 3: Configurando no Program.cs

Finalmente, registramos o MassTransit e nossa Saga na configuração de serviços da nossa aplicação.

// Program.cs

using MassTransit;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMassTransit(x =>
{
    // Adiciona a Máquina de Estados da Saga
    x.AddSagaStateMachine<OrderStateMachine, OrderState>()
        .InMemoryRepository(); // Para produção, use .EntityFrameworkRepository() ou outro repositório persistente

    // Configura o transporte (RabbitMQ)
    x.UsingRabbitMq((context, cfg) =>
    {
        cfg.Host("localhost", "/", h => {
            h.Username("guest");
            h.Password("guest");
        });

        // Configura os endpoints para sagas e consumidores automaticamente
        cfg.ConfigureEndpoints(context);
    });
});

// ... resto do Program.cs

Quando Usar (e Quando Não Usar) o Padrão Saga?

Use-o quando:

  • Você precisa garantir a consistência de dados através de múltiplos microserviços.
  • O processo de negócio é de longa duração e envolve várias etapas.
  • Ações de compensação são necessárias para reverter falhas parciais.
  • A resiliência do sistema é uma prioridade.

Pode ser excessivo quando:

  • A operação envolve apenas um único serviço e pode ser resolvida com uma transação local.
  • O processo de negócio não exige compensação em caso de falha.
  • Você está construindo um CRUD simples ou uma prova de conceito.

Conclusão: Da Inconsistência à Resiliência Orquestrada

O Padrão Saga, especialmente quando implementado com uma ferramenta poderosa como o MassTransit, transforma o caos potencial dos processos distribuídos em um fluxo de trabalho orquestrado, resiliente e transparente. Ele nos força a modelar explicitamente não apenas o “caminho feliz”, mas também os fluxos de falha e compensação, que são a essência da construção de sistemas robustos.

Ao adotar Sagas, você não está apenas escrevendo código; você está modelando a resiliência do seu negócio diretamente na sua arquitetura, garantindo que, mesmo quando as coisas dão errado, seu sistema saiba exatamente como se recuperar com confiança.