Saga Pattern Orchestration

Saga Pattern Orchestration: o maestro que nunca falha nas transações distribuídas

Imagine que você está processando um pedido de R$ 3.500,00 no seu e-commerce e o pagamento foi aprovado, mas o serviço de confirmação do pedido caiu. Resultado sem Orchestration Saga: cliente foi cobrado, mas o pedido não existe no sistema. Resultado COM Orchestration Saga: sistema automaticamente detecta a falha, estorna o pagamento e notifica o cliente - tudo sem intervenção manual.

Este é o poder do Saga Pattern Orchestration- um maestro inteligente que coordena transações distribuídas e nunca deixa seu sistema em estado inconsistente.

Demonstração rápida: veja o maestro funcionando

Antes de mergulhar na teoria, vamos ver um Orchestration Saga resolvendo o problema real:

public class OrderProcessingSaga : SagaBase
{
    protected override void InitializeSteps()
    {
        Steps.Add(new ReserveInventoryStep());    // Passo 1: Reservar estoque
        Steps.Add(new ProcessPaymentStep());      // Passo 2: Processar pagamento
        Steps.Add(new ConfirmOrderStep());        // Passo 3: Confirmar pedido
        Steps.Add(new NotifyCustomerStep());      // Passo 4: Notificar cliente
    }
}

[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
    var saga = new OrderProcessingSaga();
    var result = await saga.StartAsync(request);

    if (result.IsSuccess)
    {
        return Ok(new { OrderId = result.OrderId });
    }
    else
    {
        return BadRequest(new { Error = result.ErrorMessage });
        // Sistema já fez rollback automático de tudo!
    }
}

O que acontece por baixo dos panos:

  • Orchestrator central coordena todos os steps sequencialmente
  • Se qualquer step falhar, todos os anteriores são automaticamente compensados
  • Se step 3 falhar → system estorna pagamento (step 2) e libera estoque (step 1)
  • Estado é persistido a cada step para garantir recuperação
  • Zero código de rollback manual necessário

O que é Saga Pattern Orchestration?

Saga Orchestration é um padrão onde um coordenador central(o Orchestrator) gerencia toda a sequência de operações distribuídas, garantindo que se algo falhar, tudo seja compensado automaticamente.

A analogia do maestro de orquestra

Imagine uma orquestra sinfônica tocando uma peça complexa:

MAESTRO (Orchestrator)

O que o maestro faz quando algo dá errado:

  1. Para a música imediatamente
  2. Instrui cada seção a voltar ao estado inicial
  3. Recomeça do início ou cancela a apresentação

No Saga Orchestration é exatamente igual:

  1. Para a saga imediatamente quando step falha
  2. Executa compensação de todos os steps já executados
  3. Marca saga como falhada com estado consistente

Por que Orchestration vs outras abordagens?

Problema sem coordenação: Código frágil e perigoso

public async Task ProcessOrder(OrderRequest request)
{
    await _inventoryService.ReserveStock(request.Items);     // Service A
    await _paymentService.ProcessPayment(request.Amount);    // Service B
    await _orderService.ConfirmOrder(request.OrderId);       // Service C

    // Se Service C falhar → A e B ficam inconsistentes!
    // Precisa de código manual para desfazer A e B
}

Solução com Orchestration: Orquestração inteligente e segura

public class OrderProcessingSaga : SagaBase
{
    protected override void InitializeSteps()
    {
        Steps.Add(new ReserveInventoryStep());    // Compensation: ReleaseInventory
        Steps.Add(new ProcessPaymentStep());      // Compensation: RefundPayment
        Steps.Add(new ConfirmOrderStep());        // Compensation: CancelOrder
    }

    // Orchestrator automaticamente executa compensação na ordem reversa
    // Se qualquer step falhar → rollback automático e estado consistente
}

Implementação

Vamos construir um Saga Orchestration:

1. Base class do Orchestrator

public abstract class SagaBase
{
    public Guid SagaId { get; } = Guid.NewGuid();
    public EnumSagaStatus Status { get; private set; } = EnumSagaStatus.Running;
    public List<ISagaStep> Steps { get; } = new();
    public List<SagaStepHistory> StepHistory { get; } = new();

    private readonly ISagaRepository _sagaRepository;
    private readonly ISagaMetrics _sagaMetrics;
    private readonly ILogger<SagaBase> _logger;

    protected abstract void InitializeSteps();

    public async Task<SagaResult> StartAsync(CancellationToken cancellationToken = default)
    {
        var stopwatch = Stopwatch.StartNew();

        try
        {
            _sagaMetrics.RecordSagaStarted(GetType().Name);
            _logger.LogInformation("Starting saga {SagaId} of type {SagaType}", SagaId, GetType().Name);

            InitializeSteps();

            // Persistir estado inicial
            await _sagaRepository.SaveAsync(GetSagaState());

            foreach (var step in Steps)
            {
                var stepResult = await ExecuteStepWithRetryAsync(step, cancellationToken);

                if (!stepResult.IsSuccess)
                {
                    _logger.LogWarning("Step {StepName} failed for saga {SagaId}: {Error}",
                        step.StepName, SagaId, stepResult.ErrorMessage);

                    await CompensateAsync();
                    return SagaResult.Failed(stepResult.ErrorMessage);
                }

                // Atualizar estado após cada step
                RecordStepSuccess(step, stepResult);
                await _sagaRepository.UpdateAsync(GetSagaState());
            }

            Status = EnumSagaStatus.Completed;
            await _sagaRepository.UpdateAsync(GetSagaState());

            stopwatch.Stop();
            _sagaMetrics.RecordSagaCompleted(GetType().Name, Status, stopwatch.Elapsed);

            _logger.LogInformation("Saga {SagaId} completed successfully in {Duration}ms",
                SagaId, stopwatch.ElapsedMilliseconds);

            return SagaResult.Success();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Saga {SagaId} failed with exception", SagaId);
            await CompensateAsync();

            stopwatch.Stop();
            _sagaMetrics.RecordSagaCompleted(GetType().Name, EnumSagaStatus.Failed, stopwatch.Elapsed);

            return SagaResult.Failed(ex.Message);
        }
    }

    private async Task<StepResult> ExecuteStepWithRetryAsync(ISagaStep step, CancellationToken cancellationToken)
    {
        var retryPolicy = Policy
            .Handle<Exception>(ex => IsRetryableException(ex))
            .WaitAndRetryAsync(
                retryCount: 3,
                sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
                onRetry: (outcome, timespan, retryCount, context) =>
                {
                    _logger.LogWarning("Retrying step {StepName} (attempt {RetryCount}) after {Delay}ms",
                        step.StepName, retryCount, timespan.TotalMilliseconds);
                });

        return await retryPolicy.ExecuteAsync(async () =>
        {
            using var timeoutCts = new CancellationTokenSource(step.Timeout);
            using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
                cancellationToken, timeoutCts.Token);

            var command = BuildCommandForStep(step);
            return await step.ExecuteAsync(command, combinedCts.Token);
        });
    }

    private async Task CompensateAsync()
    {
        Status = EnumSagaStatus.CompensationRequired;
        await _sagaRepository.UpdateAsync(GetSagaState());

        _logger.LogWarning("Starting compensation for saga {SagaId}", SagaId);

        // Compensar na ordem REVERSA (último executado primeiro)
        var executedSteps = StepHistory
            .Where(h => h.Status == EnumSagaStatus.Completed)
            .OrderByDescending(h => h.ExecutedAt)
            .ToList();

        var compensationFailures = new List<string>();

        foreach (var stepHistory in executedSteps)
        {
            try
            {
                var step = Steps.First(s => s.StepName == stepHistory.StepName);
                var command = GetCommandForStep(step);

                _logger.LogInformation("Compensating step {StepName} for saga {SagaId}",
                    step.StepName, SagaId);

                await step.CompensateAsync(command);

                RecordStepCompensated(step);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to compensate step {StepName} for saga {SagaId}",
                    stepHistory.StepName, SagaId);

                compensationFailures.Add($"{stepHistory.StepName}: {ex.Message}");
                RecordStepCompensationFailed(stepHistory.StepName, ex.Message);

                // Alertar operações para intervenção manual
                await _alertingService.SendCriticalAlertAsync(
                    $"Compensation failed for step {stepHistory.StepName} in saga {SagaId}");
            }
        }

        if (compensationFailures.Any())
        {
            Status = EnumSagaStatus.CompensationFailed;
            await _sagaRepository.UpdateAsync(GetSagaState());

            throw new CompensationException($"Failed to compensate: {string.Join(", ", compensationFailures)}");
        }
        else
        {
            Status = EnumSagaStatus.Compensated;
            await _sagaRepository.UpdateAsync(GetSagaState());

            _logger.LogInformation("Successfully compensated saga {SagaId}", SagaId);
        }
    }
}

public enum EnumSagaStatus
{
    Running,
    Completed,
    Failed,
    CompensationRequired,
    Compensated,
    CompensationFailed
}

2. Interface e base para Steps

public interface ISagaStep
{
    string StepName { get; }
    TimeSpan Timeout { get; }
    Task<StepResult> ExecuteAsync(object command, CancellationToken cancellationToken = default);
    Task CompensateAsync(object command, CancellationToken cancellationToken = default);
}

public abstract class SagaStepBase<TCommand, TResponse> : ISagaStep
{
    protected readonly ILogger<SagaStepBase<TCommand, TResponse>> _logger;

    public abstract string StepName { get; }
    public abstract TimeSpan Timeout { get; }

    public abstract Task<TResponse> ExecuteAsync(TCommand command, CancellationToken cancellationToken = default);
    public abstract Task CompensateAsync(TCommand command, CancellationToken cancellationToken = default);

    public async Task<StepResult> ExecuteAsync(object command, CancellationToken cancellationToken = default)
    {
        if (command is not TCommand typedCommand)
        {
            throw new ArgumentException($"Expected {typeof(TCommand).Name} but got {command.GetType().Name}");
        }

        try
        {
            var result = await ExecuteAsync(typedCommand, cancellationToken);
            return StepResult.Success(result);
        }
        catch (Exception ex) when (IsBusinessException(ex))
        {
            return StepResult.Failed(ex.Message, isRetryable: false);
        }
        catch (Exception ex)
        {
            return StepResult.Failed(ex.Message, isRetryable: true);
        }
    }

    public async Task CompensateAsync(object command, CancellationToken cancellationToken = default)
    {
        if (command is not TCommand typedCommand)
        {
            throw new ArgumentException($"Expected {typeof(TCommand).Name} but got {command.GetType().Name}");
        }

        await CompensateAsync(typedCommand, cancellationToken);
    }

    protected virtual bool IsBusinessException(Exception ex)
    {
        return ex is BusinessLogicException || ex is ValidationException;
    }
}

public class StepResult
{
    public bool IsSuccess { get; private set; }
    public string ErrorMessage { get; private set; } = string.Empty;
    public bool IsRetryable { get; private set; }
    public object? Data { get; private set; }

    public static StepResult Success(object? data = null) => new() { IsSuccess = true, Data = data };
    public static StepResult Failed(string error, bool isRetryable = true) => new()
    {
        IsSuccess = false,
        ErrorMessage = error,
        IsRetryable = isRetryable
    };
}

3. Step concreto: Reservar estoque

public class ReserveInventoryStep : SagaStepBase<ReserveInventoryCommand, InventoryReservedResponse>
{
    private readonly IInventoryService _inventoryService;

    public override string StepName => "ReserveInventory";
    public override TimeSpan Timeout => TimeSpan.FromSeconds(30);

    public override async Task<InventoryReservedResponse> ExecuteAsync(
        ReserveInventoryCommand command,
        CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Reserving inventory for order {OrderId} with {ItemCount} items",
            command.OrderId, command.Items.Count);

        var reservationIds = new List<Guid>();

        foreach (var item in command.Items)
        {
            // Verificar disponibilidade primeiro (operação idempotente)
            var availability = await _inventoryService.CheckAvailabilityAsync(
                item.ProductId, item.Quantity, cancellationToken);

            if (!availability.IsAvailable)
            {
                _logger.LogWarning("Insufficient stock for product {ProductId}. Available: {Available}, Requested: {Requested}",
                    item.ProductId, availability.AvailableQuantity, item.Quantity);

                throw new InsufficientStockException(
                    $"Product {item.ProductId} has only {availability.AvailableQuantity} units available, but {item.Quantity} were requested");
            }

            // Reservar estoque (operação atômica)
            var reservation = await _inventoryService.ReserveAsync(
                item.ProductId, item.Quantity, command.OrderId, cancellationToken);

            reservationIds.Add(reservation.Id);

            _logger.LogInformation("Reserved {Quantity} units of product {ProductId} with reservation {ReservationId}",
                item.Quantity, item.ProductId, reservation.Id);
        }

        var response = new InventoryReservedResponse
        {
            OrderId = command.OrderId,
            ReservationIds = reservationIds,
            ExpiresAt = DateTime.UtcNow.AddMinutes(15), // TTL para limpeza automática
            TotalItemsReserved = command.Items.Sum(i => i.Quantity)
        };

        _logger.LogInformation("Successfully reserved inventory for order {OrderId}. Reservations: {ReservationIds}",
            command.OrderId, string.Join(", ", reservationIds));

        return response;
    }

    public override async Task CompensateAsync(
        ReserveInventoryCommand command,
        CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Compensating inventory reservation for order {OrderId}", command.OrderId);

        try
        {
            // Buscar todas as reservas feitas para este pedido
            var reservations = await _inventoryService.GetReservationsByOrderAsync(
                command.OrderId, cancellationToken);

            if (!reservations.Any())
            {
                _logger.LogInformation("No reservations found for order {OrderId}. Compensation not needed.",
                    command.OrderId);
                return;
            }

            foreach (var reservation in reservations)
            {
                // Operação idempotente - se já foi liberada, não faz nada
                await _inventoryService.ReleaseReservationAsync(reservation.Id, cancellationToken);

                _logger.LogInformation("Released reservation {ReservationId} for product {ProductId} (quantity: {Quantity})",
                    reservation.Id, reservation.ProductId, reservation.Quantity);
            }

            _logger.LogInformation("Successfully compensated inventory reservation for order {OrderId}. Released {Count} reservations.",
                command.OrderId, reservations.Count());
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to compensate inventory reservation for order {OrderId}. Manual intervention required!",
                command.OrderId);

            // Escalar para operações
            await _alertingService.SendCriticalAlertAsync(
                $"Failed to release inventory reservation for order {command.OrderId}. Manual cleanup required!");

            throw; // Re-throw para marcar compensação como falhada
        }
    }
}

Command e Response models

public record ReserveInventoryCommand
{
    public Guid OrderId { get; init; }
    public List<OrderItem> Items { get; init; } = new();
    public DateTime RequestedAt { get; init; } = DateTime.UtcNow;
}

public record InventoryReservedResponse
{
    public Guid OrderId { get; init; }
    public List<Guid> ReservationIds { get; init; } = new();
    public DateTime ExpiresAt { get; init; }
    public int TotalItemsReserved { get; init; }
}

public record OrderItem
{
    public Guid ProductId { get; init; }
    public int Quantity { get; init; }
    public decimal UnitPrice { get; init; }
}

4. Step de pagamento com retry inteligente

public class ProcessPaymentStep : SagaStepBase<ProcessPaymentCommand, PaymentProcessedResponse>
{
    private readonly IPaymentGateway _paymentGateway;

    public override string StepName => "ProcessPayment";
    public override TimeSpan Timeout => TimeSpan.FromMinutes(2); // Payments podem demorar

    public override async Task<PaymentProcessedResponse> ExecuteAsync(
        ProcessPaymentCommand command,
        CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Processing payment for order {OrderId} amount {Amount} {Currency}",
            command.OrderId, command.Amount, command.Currency);

        // Verificar se pagamento já foi processado (idempotência)
        var existingTransaction = await _paymentGateway.GetTransactionByOrderIdAsync(
            command.OrderId, cancellationToken);

        if (existingTransaction != null)
        {
            _logger.LogInformation("Payment already processed for order {OrderId}. Transaction: {TransactionId}",
                command.OrderId, existingTransaction.TransactionId);

            return new PaymentProcessedResponse
            {
                OrderId = command.OrderId,
                TransactionId = existingTransaction.TransactionId,
                Amount = existingTransaction.Amount,
                Currency = existingTransaction.Currency,
                ProcessedAt = existingTransaction.ProcessedAt,
                Status = existingTransaction.Status
            };
        }

        // Processar pagamento com retry para falhas transitórias
        var paymentRequest = new PaymentRequest
        {
            OrderId = command.OrderId,
            Amount = command.Amount,
            Currency = command.Currency,
            PaymentMethod = command.PaymentMethod,
            CardToken = command.CardToken,
            IdempotencyKey = $"order-{command.OrderId}-{DateTime.UtcNow:yyyyMMdd}" // Chave de idempotência
        };

        var paymentResult = await _paymentGateway.ProcessPaymentAsync(paymentRequest, cancellationToken);

        switch (paymentResult.Status)
        {
            case PaymentStatus.Approved:
                _logger.LogInformation("Payment approved for order {OrderId}. TransactionId: {TransactionId}",
                    command.OrderId, paymentResult.TransactionId);

                return new PaymentProcessedResponse
                {
                    OrderId = command.OrderId,
                    TransactionId = paymentResult.TransactionId,
                    Amount = paymentResult.Amount,
                    Currency = paymentResult.Currency,
                    ProcessedAt = paymentResult.ProcessedAt,
                    Status = PaymentStatus.Approved
                };

            case PaymentStatus.Declined:
                _logger.LogWarning("Payment declined for order {OrderId}. Reason: {Reason}",
                    command.OrderId, paymentResult.DeclineReason);

                throw new PaymentDeclinedException(paymentResult.DeclineReason ?? "Payment was declined");

            case PaymentStatus.Pending:
                _logger.LogInformation("Payment is pending for order {OrderId}. Will retry...", command.OrderId);
                throw new PaymentPendingException("Payment is still being processed");

            default:
                throw new PaymentProcessingException($"Unexpected payment status: {paymentResult.Status}");
        }
    }

    public override async Task CompensateAsync(
        ProcessPaymentCommand command,
        CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Compensating payment for order {OrderId}", command.OrderId);

        try
        {
            // Buscar transação original
            var transaction = await _paymentGateway.GetTransactionByOrderIdAsync(
                command.OrderId, cancellationToken);

            if (transaction == null)
            {
                _logger.LogInformation("No payment transaction found for order {OrderId}. Compensation not needed.",
                    command.OrderId);
                return;
            }

            if (transaction.Status != PaymentStatus.Approved)
            {
                _logger.LogInformation("Payment for order {OrderId} was not approved (status: {Status}). No refund needed.",
                    command.OrderId, transaction.Status);
                return;
            }

            // Verificar se refund já foi processado
            var existingRefund = await _paymentGateway.GetRefundByTransactionIdAsync(
                transaction.TransactionId, cancellationToken);

            if (existingRefund != null)
            {
                _logger.LogInformation("Refund already processed for transaction {TransactionId}. RefundId: {RefundId}",
                    transaction.TransactionId, existingRefund.RefundId);
                return;
            }

            // Processar refund
            var refundRequest = new RefundRequest
            {
                OriginalTransactionId = transaction.TransactionId,
                Amount = transaction.Amount,
                Reason = "Order cancelled due to saga failure",
                IdempotencyKey = $"refund-{transaction.TransactionId}-{DateTime.UtcNow:yyyyMMdd}"
            };

            var refundResult = await _paymentGateway.RefundAsync(refundRequest, cancellationToken);

            if (refundResult.Status == RefundStatus.Approved)
            {
                _logger.LogInformation("Successfully refunded payment for order {OrderId}. RefundId: {RefundId}",
                    command.OrderId, refundResult.RefundId);
            }
            else
            {
                _logger.LogError("Failed to refund payment for order {OrderId}. Status: {Status}, Reason: {Reason}",
                    command.OrderId, refundResult.Status, refundResult.DeclineReason);

                throw new RefundFailedException(
                    $"Refund failed with status {refundResult.Status}: {refundResult.DeclineReason}");
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to compensate payment for order {OrderId}. Manual refund required!",
                command.OrderId);

            // Escalar para operações financeiras
            await _alertingService.SendCriticalAlertAsync(
                $"Failed to refund payment for order {command.OrderId}. Manual refund required! Exception: {ex.Message}");

            throw; // Critical: payment compensation must succeed
        }
    }
}

Vantagens do Orchestration Saga

Benefícios principais:

1. Controle centralizado

  • Debugging simplificado: Todo fluxo visível em um lugar
  • Lógica de negócio clara: Sequência de steps bem definida
  • Monitoramento fácil: Estado centralizado para observar

2. Tratamento de falhas robusto

  • Compensação automática: Rollback na ordem correta
  • Retry inteligente: Diferencia falhas transitórias vs permanentes
  • Estado persistido: Recuperação após restart da aplicação

3. Testabilidade

  • Steps isolados: Cada step pode ser testado independentemente
  • Mock fácil: Substituir dependencies dos steps em testes
  • Cenários de falha: Simular falhas em qualquer step

4. Evolução controlada

  • Versionamento: Adicionar/remover steps sem quebrar sagas em andamento
  • A/B testing: Diferentes implementações de steps
  • Rollback seguro: Voltar versão anterior sem perder sagas ativas

Armadilhas do Orchestration e como evitá-las

Problemas comuns:

1. Orchestrator como bottleneck

PERIGOSO- todos os pedidos passam pelo mesmo orchestrator

Vai virar gargalo com alto volume!

public class SingletonOrderOrchestrator
{
    private static readonly OrderOrchestrator _instance = new();
}

Solução:

SEGURO- instância por saga Nova instância para cada pedido

public class OrdersController : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
    {
        var saga = new OrderProcessingSaga(_serviceProvider);
        var result = await saga.StartAsync(request);

        return result.IsSuccess ? Ok(result) : BadRequest(result);
    }
}

2. Steps muito grandes e acoplados

PERIGOSO- step faz muita coisa

public class ProcessOrderStep : SagaStepBase<ProcessOrderCommand, ProcessOrderResponse>
{
    public override async Task<ProcessOrderResponse> ExecuteAsync(ProcessOrderCommand command)
    {
        // Fazendo tudo em um step
        await ValidateCustomer(command.CustomerId);
        await ReserveInventory(command.Items);
        await ProcessPayment(command.Amount);
        await GenerateInvoice(command.OrderId);
        await SendEmail(command.CustomerEmail);

        // Se qualquer coisa falhar, perde todo o trabalho!
    }
}

Solução:

// SEGURO - steps granulares
public class OrderProcessingSaga : SagaBase
{
    protected override void InitializeSteps()
    {
        Steps.Add(new ValidateCustomerStep());     // Step 1: rápido e simples
        Steps.Add(new ReserveInventoryStep());     // Step 2: pode falhar por estoque
        Steps.Add(new ProcessPaymentStep());       // Step 3: pode falhar por cartão
        Steps.Add(new GenerateInvoiceStep());      // Step 4: raramente falha
        Steps.Add(new SendEmailStep());           // Step 5: não crítico
    }
}

3. Não implementar timeouts

PERIGOSO- pode travar para sempre Se gateway travar, saga trava para sempre

public override async Task<PaymentResponse> ExecuteAsync(PaymentCommand command)
{
    return await _paymentGateway.ProcessAsync(command);
}

Solução:

SEGURO- timeout em todos os steps

public override TimeSpan Timeout => TimeSpan.FromMinutes(2);

public override async Task<PaymentResponse> ExecuteAsync(
    PaymentCommand command,
    CancellationToken cancellationToken)
{
    using var timeoutCts = new CancellationTokenSource(Timeout);
    using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
        cancellationToken, timeoutCts.Token);

    try
    {
        return await _paymentGateway.ProcessAsync(command, combinedCts.Token);
    }
    catch (OperationCanceledException) when (timeoutCts.Token.IsCancellationRequested)
    {
        throw new StepTimeoutException($"Payment step timed out after {Timeout}");
    }
}

Quando usar Orchestration Saga

Use Orchestration quando:

1. Processo de negócio complexo e bem definido

  • E-commerce order processing
  • Loan approval workflows
  • Insurance claims processing
  • Multi-step onboarding processes

2. Debugging e auditoria são críticos

  • Sistemas financeiros
  • Healthcare applications
  • Compliance-heavy industries

3. Controle fino sobre o fluxo é necessário

  • Conditional branching baseado em resultados
  • Parallel execution de alguns steps
  • Dynamic step selection baseado em contexto

4. Equipe centralizada ou pequena

  • Time pequeno que pode manter orchestrator
  • Ownership centralizado do processo de negócio

NÃO use quando:

1. Performance extrema é necessária

  • Orchestrator adiciona latência de network
  • Steps sequenciais podem ser lentos

2. Serviços completamente autônomos

  • Teams independentes querem controle total
  • Microservices com bounded contexts bem definidos

3. Escalabilidade horizontal extrema

  • Milhões de sagas simultâneas
  • Orchestrator pode virar bottleneck (gargalo)

Resumo: o maestro que nunca falha

Saga Orchestration é o padrão ideal quando você precisa de controle centralizado sobre transações distribuídas complexas.

Próximo passo:

No próximo artigo, vamos explorar Saga Choreography- a abordagem descentralizada onde serviços se coordenam através de eventos, oferecendo melhor performance e escalabilidade, mas com complexidade adicional de debugging.

Próximos passos:

  • Implemente os exemplos em um projeto teste
  • Configure monitoramento desde o primeiro dia
  • Comece com casos de uso simples antes de processos complexos

Recursos adicionais:

Gostou do conteúdo? Conecte-se comigo no LinkedIn para mais artigos sobre arquitetura de software e desenvolvimento .NET!

Compartilhe este artigo

Quer modernizar seu sistema?

Saiba mais sobre como modernizar suas aplicações e escalar seu negócio com tecnologia de ponta.

Fale com um especialista
Felipe Marciano

Sobre o Autor

Felipe Marciano

Felipe Marciano é um desenvolvedor apaixonado por tecnologia, especializado em .NET Core, Angular e soluções cloud-native. Com mais de 12 anos de experiência, dedica-se à modernização de sistemas legados e à arquitetura de microsserviços, sempre priorizando código limpo, boas práticas e soluções realmente escaláveis. Felipe busca inovação constante em novas ferramentas e frameworks para garantir alta qualidade e ótima experiência do usuário em cada projeto que lidera.

Posts Recomendados