Desvendando o Padrão Saga com .NET 9, MassTransit e RabbitMQ: Orquestrando Microserviços com Confiança
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:
- O
Serviço de Pedidos
cria um novo pedido. - O
Serviço de Pagamentos
processa o cartão de crédito. - 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:
- Ação 1: Reservar um voo. (Compensação: Cancelar a reserva do voo).
- Ação 2: Reservar um hotel. (Compensação: Cancelar a reserva do hotel).
- 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:
- 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).
- 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.