Saga Orchestration Pattern Implementation

In modern distributed systems especially microservices architectures, maintaining data consistency across multiple services is one of the hardest problems to solve. Traditional ACID transactions don’t scale well across service boundaries, leading architects to adopt eventual consistency patterns instead.

One of the most widely used solutions is the Saga Pattern. This post focuses Complete Saga Implementation with Compensating Transactions

Complete Saga Orchestration Implementation Guide with Compensating Transactions

Getting Started

As systems evolve from monolithic architectures to distributed microservices, managing complex business workflows becomes increasingly challenging. Operations that were once handled within a single transactional boundary now span multiple services, each with its own data store and lifecycle. Ensuring consistency, reliability, and clear execution flow across these services requires a structured coordination mechanism.

The Orchestration Pattern addresses this challenge by introducing a centralized coordinator—known as the orchestrator—that manages and directs interactions between services. Rather than allowing services to communicate with each other in an ad hoc manner, the orchestrator explicitly defines the sequence of operations, invokes each service, and determines how the system should respond to successes or failures. This approach provides a clear, controlled workflow, making complex business processes easier to design, monitor, and maintain.

By separating workflow management from service implementation, the Orchestration Pattern enables better visibility, stronger error handling, and improved maintainability in distributed systems. It is especially valuable for long-running transactions and business processes where execution order, compensation, and reliability are critical.

Orchestration Pattern in .NET – Example

Below is a fully usable .NET 8 console application example implementing the Orchestration (Saga) Pattern with:
Steps:

  1. Create Order
  2. Reserve Inventory
  3. Process Payment
  4. Arrange Shipping
If any step fails, compensating actions are triggered.
Full Working Code

Create a console application in .Net with name SagaOrchestrationDemo and replace Program.cs with the following:

 using System;  
 using System.Collections.Generic;  
 using System.Threading.Tasks;  
 namespace SagaOrchestrationDemo  
 {  
   #region Contracts  
   public interface ISagaStep  
   {  
     string Name { get; }  
     Task ExecuteAsync(SagaContext context);  
     Task CompensateAsync(SagaContext context);  
   }  
   #endregion  
   #region Context  
   public class SagaContext  
   {  
     public Guid OrderId { get; set; }  
     public bool InventoryReserved { get; set; }  
     public bool PaymentProcessed { get; set; }  
     public bool ShippingArranged { get; set; }  
   }  
   #endregion  
   #region Orchestrator  
   public class SagaOrchestrator  
   {  
     private readonly IList<ISagaStep> _steps;  
     public SagaOrchestrator(IList<ISagaStep> steps)  
     {  
       _steps = steps;  
     }  
     public async Task ExecuteAsync(SagaContext context)  
     {  
       var completedSteps = new Stack<ISagaStep>();  
       try  
       {  
         foreach (var step in _steps)  
         {  
           Console.WriteLine($"\n➡ Executing: {step.Name}");  
           await step.ExecuteAsync(context);  
           completedSteps.Push(step);  
         }  
         Console.WriteLine("\n🎉 Saga completed successfully!");  
       }  
       catch (Exception ex)  
       {  
         Console.WriteLine($"\n❌ Error: {ex.Message}");  
         Console.WriteLine("🔁 Starting compensation...");  
         while (completedSteps.Count > 0)  
         {  
           var step = completedSteps.Pop();  
           Console.WriteLine($"↩ Compensating: {step.Name}");  
           await step.CompensateAsync(context);  
         }  
         Console.WriteLine("⚠ Saga rolled back.");  
       }  
     }  
   }  
   #endregion  
   #region Steps  
   public class CreateOrderStep : ISagaStep  
   {  
     public string Name => "Create Order";  
     public Task ExecuteAsync(SagaContext context)  
     {  
       context.OrderId = Guid.NewGuid();  
       Console.WriteLine($"Order created: {context.OrderId}");  
       return Task.CompletedTask;  
     }  
     public Task CompensateAsync(SagaContext context)  
     {  
       Console.WriteLine($"Order deleted: {context.OrderId}");  
       return Task.CompletedTask;  
     }  
   }  
   public class ReserveInventoryStep : ISagaStep  
   {  
     public string Name => "Reserve Inventory";  
     public Task ExecuteAsync(SagaContext context)  
     {  
       SimulateRandomFailure();  
       context.InventoryReserved = true;  
       Console.WriteLine("Inventory reserved.");  
       return Task.CompletedTask;  
     }  
     public Task CompensateAsync(SagaContext context)  
     {  
       if (context.InventoryReserved)  
         Console.WriteLine("Inventory released.");  
       return Task.CompletedTask;  
     }  
     private void SimulateRandomFailure()  
     {  
       if (Random.Shared.Next(0, 5) == 1)  
         throw new Exception("Inventory reservation failed!");  
     }  
   }  
   public class ProcessPaymentStep : ISagaStep  
   {  
     public string Name => "Process Payment";  
     public Task ExecuteAsync(SagaContext context)  
     {  
       SimulateRandomFailure();  
       context.PaymentProcessed = true;  
       Console.WriteLine("Payment processed.");  
       return Task.CompletedTask;  
     }  
     public Task CompensateAsync(SagaContext context)  
     {  
       if (context.PaymentProcessed)  
         Console.WriteLine("Payment refunded.");  
       return Task.CompletedTask;  
     }  
     private void SimulateRandomFailure()  
     {  
       if (Random.Shared.Next(0, 5) == 2)  
         throw new Exception("Payment processing failed!");  
     }  
   }  
   public class ArrangeShippingStep : ISagaStep  
   {  
     public string Name => "Arrange Shipping";  
     public Task ExecuteAsync(SagaContext context)  
     {  
       SimulateRandomFailure();  
       context.ShippingArranged = true;  
       Console.WriteLine("Shipping arranged.");  
       return Task.CompletedTask;  
     }  
     public Task CompensateAsync(SagaContext context)  
     {  
       if (context.ShippingArranged)  
         Console.WriteLine("Shipping canceled.");  
       return Task.CompletedTask;  
     }  
     private void SimulateRandomFailure()  
     {  
       if (Random.Shared.Next(0, 5) == 3)  
         throw new Exception("Shipping arrangement failed!");  
     }  
   }  
   #endregion  
   class Program  
   {  
     static async Task Main(string[] args)  
     {  
       var steps = new List<ISagaStep>  
       {  
         new CreateOrderStep(),  
         new ReserveInventoryStep(),  
         new ProcessPaymentStep(),  
         new ArrangeShippingStep()  
       };  
       var orchestrator = new SagaOrchestrator(steps);  
       var context = new SagaContext();  
       await orchestrator.ExecuteAsync(context);  
       Console.WriteLine("\nPress any key to exit...");  
       Console.ReadKey();  
     }  
   }  
 }  

How It Works
  • ISagaStep → defines Execute + Compensate
  • SagaOrchestrator → runs steps and handles rollback
  • Each business step implements compensation logic
  • Random failures simulate real-world errors

What This Demonstrates
  • True Orchestration (central coordinator)
  • Ordered execution
  • Reverse-order compensation
  • Isolation between steps
  • Easy to extend with new steps
  • Async-ready

Example Failure Scenario
If Payment fails, you’ll see:
 Order created  
 Inventory reserved  
 Error: Payment processing failed!  
 Starting compensation...  
 Compensating: Reserve Inventory  
 Inventory released  
 Compensating: Create Order  
 Order deleted  
 Saga rolled back.  

Summary

The Saga Orchestration Pattern is a powerful solution for managing data consistency in distributed systems. By centralizing workflow control and handling failures through compensating actions, it enables scalable, resilient microservice architectures without relying on distributed transactions.

Thanks

Kailash Chandra Behera

I am an IT professional with over 13 years of experience in the full software development life cycle for Windows, services, and web-based applications using Microsoft .NET technologies.

Previous Post Next Post

نموذج الاتصال