Skip to content

Saga Pattern: Distributed Transactions

Saga Pattern: Distributed Transactions

The Saga Pattern is a failure-management pattern for distributed systems. In a microservices architecture, you cannot use traditional ACID transactions across services. A Saga provides eventual consistency by breaking a large transaction into a series of small, local transactions.

๐Ÿ—๏ธ The Problem

Imagine an E-commerce System. When a user places an order:

  1. Order Service creates an order.
  2. Payment Service processes payment.
  3. Inventory Service reserves stock.

If the payment succeeds but the inventory is out of stock, you need to โ€œundoโ€ the payment. Since there is no global transaction, you use a Saga to manage this.

๐Ÿš€ Two Types of Sagas

1. Choreography (Event-Based)

Each service produces and listens to events from other services.

  • Pros: Simple for small workflows, no central point of failure.
  • Cons: Hard to track the state of a complex workflow.

2. Orchestration (Command-Based)

A central โ€œOrchestratorโ€ (Saga State Machine) tells each service what to do and when.

  • Pros: Centralized logic, easy to debug complex workflows.
  • Cons: The orchestrator can become a โ€œGod Object.โ€

๐Ÿš€ The .NET Implementation (Orchestration with MassTransit)

In .NET, MassTransit is the gold standard for implementing Sagas.

1. The Saga State (Data)

public class OrderState : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; } // Unique ID for the Saga
    public string CurrentState { get; set; } // e.g., "Submitted", "Paid", "Failed"
    public Guid OrderId { get; set; }
}

2. The State Machine (Logic)

public class OrderSaga : MassTransitStateMachine<OrderState>
{
    public State Submitted { get; private set; }
    public State Paid { get; private set; }

    public Event<OrderSubmitted> OrderSubmitted { get; private set; }
    public Event<PaymentConfirmed> PaymentConfirmed { get; private set; }

    public OrderSaga()
    {
        InstanceState(x => x.CurrentState);

        Initially(
            When(OrderSubmitted)
                .Then(context => context.Saga.OrderId = context.Message.OrderId)
                .TransitionTo(Submitted)
                .Publish(context => new ProcessPayment { OrderId = context.Saga.OrderId })
        );

        During(Submitted,
            When(PaymentConfirmed)
                .TransitionTo(Paid)
                .Publish(context => new ReserveInventory { OrderId = context.Saga.OrderId })
        );
    }
}

๐Ÿ“‰ Compensating Transactions

If a step fails, the Saga must trigger โ€œCompensating Transactionsโ€ to undo the previous steps (e.g., RefundPayment if ReserveInventory fails).

๐Ÿ’ก Why use Saga?

  • Consistency: Ensures distributed systems reach a consistent state.
  • Resilience: Handles partial failures gracefully.
  • Scalability: Services remain independent and asynchronous.