---
name: saga-pattern
description: SAGA Pattern - quản lý distributed transactions qua chuỗi local transactions với compensating actions. Use for checkout flows, order processing, và multi-service orchestration.
compatibility: ".NET 10+, MassTransit, RabbitMQ, EF Core"
metadata:
  author: Velik Ho
  version: "1.0"
---

# SAGA Pattern / Mẫu SAGA

Pattern quản lý distributed transactions trong microservices bằng chuỗi local transactions.

## When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:
- Multi-service transactions required / Cần transactions đa dịch vụ
- Distributed transaction (2PC) not feasible / 2PC không khả thi
- Long-running business processes / Quy trình nghiệp vụ chạy lâu
- Need compensating transactions / Cần transactions bù trừ

## Core Concepts / Khái Niệm Cốt Lõi

### Why SAGA? / Tại Sao Dùng SAGA?

```
┌─────────────────────────────────────────────────────────────┐
│     ❌ DISTRIBUTED TRANSACTION (2PC) - PROBLEMS             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌───────┐     ┌───────┐     ┌───────┐                    │
│   │Order  │────▶│Payment│────▶│Inventory│                   │
│   │ DB    │     │ DB    │     │ DB      │                   │
│   └───────┘     └───────┘     └───────┘                    │
│       │             │             │                         │
│       └─────────────┴─────────────┘                         │
│              Transaction Coordinator                        │
│                                                             │
│   Problems:                                                 │
│   • Locks resources across services                         │
│   • Single point of failure                                 │
│   • Poor performance                                        │
│   • Not always supported (NoSQL, external APIs)            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│              ✅ SAGA - LOCAL TRANSACTIONS                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Step 1          Step 2          Step 3                    │
│   ┌───────┐      ┌───────┐      ┌───────┐                  │
│   │Create │─────▶│Reserve│─────▶│Charge │                  │
│   │Order  │      │Stock  │      │Payment│                  │
│   └───────┘      └───────┘      └───────┘                  │
│       │              │              │                       │
│       ▼              ▼              ▼                       │
│   [Local Tx]    [Local Tx]    [Local Tx]                   │
│                                                             │
│   If Step 3 fails:                                         │
│   ┌───────┐      ┌───────┐                                 │
│   │Release│◀─────│Cancel │                                 │
│   │Stock  │      │Order  │                                 │
│   └───────┘      └───────┘                                 │
│   Compensate     Compensate                                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### Choreography vs Orchestration

```
┌─────────────────────────────────────────────────────────────┐
│                    CHOREOGRAPHY                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Order     Event Bus      Inventory      Payment           │
│     │           │              │             │              │
│     │──Create──▶│              │             │              │
│     │           │──OrderCreated─▶│           │              │
│     │           │              │──Reserve──▶│              │
│     │           │              │             │──Charge────▶ │
│     │           │◀─────PaymentProcessed──────│              │
│                                                             │
│   ✅ Loose coupling, simple services                        │
│   ❌ Hard to track flow, complex rollback                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                    ORCHESTRATION                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                    ┌─────────────┐                          │
│                    │    SAGA     │                          │
│                    │ Orchestrator│                          │
│                    └─────┬───────┘                          │
│           ┌──────────────┼──────────────┐                   │
│           ▼              ▼              ▼                   │
│      ┌─────────┐   ┌─────────┐   ┌─────────┐               │
│      │ Order   │   │Inventory│   │ Payment │               │
│      │ Service │   │ Service │   │ Service │               │
│      └─────────┘   └─────────┘   └─────────┘               │
│                                                             │
│   ✅ Clear flow, centralized error handling                 │
│   ❌ More coupling, orchestrator complexity                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
```

### SAGA Components / Thành Phần SAGA

| Component | Purpose | Example |
|-----------|---------|---------|
| **Saga State Machine** | Tracks saga state | OrderSaga |
| **Saga Step** | Individual transaction | ReserveInventory |
| **Compensating Action** | Undo step on failure | ReleaseInventory |
| **Saga Data** | Shared state across steps | OrderId, Items |

## Key Patterns / Mẫu Chính

### Saga State Machine with MassTransit

```csharp
/// <summary>
/// EN: Order saga state tracking.
/// VI: Theo dõi state của Order saga.
/// </summary>
public class OrderSagaState : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; }
    public string CurrentState { get; set; } = default!;
    
    // EN: Saga data / VI: Dữ liệu saga
    public Guid OrderId { get; set; }
    public string UserId { get; set; } = default!;
    public decimal TotalAmount { get; set; }
    public string? PaymentId { get; set; }
    public string? FailureReason { get; set; }
}

/// <summary>
/// EN: Order saga state machine definition.
/// VI: Định nghĩa state machine cho Order saga.
/// </summary>
public class OrderSaga : MassTransitStateMachine<OrderSagaState>
{
    // EN: States / VI: Các trạng thái
    public State OrderCreated { get; private set; } = null!;
    public State InventoryReserved { get; private set; } = null!;
    public State PaymentProcessed { get; private set; } = null!;
    public State Completed { get; private set; } = null!;
    public State Failed { get; private set; } = null!;

    // EN: Events / VI: Các sự kiện
    public Event<OrderSubmitted> OrderSubmittedEvent { get; private set; } = null!;
    public Event<InventoryReserved> InventoryReservedEvent { get; private set; } = null!;
    public Event<InventoryReservationFailed> InventoryFailedEvent { get; private set; } = null!;
    public Event<PaymentProcessed> PaymentProcessedEvent { get; private set; } = null!;
    public Event<PaymentFailed> PaymentFailedEvent { get; private set; } = null!;

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

        // EN: Define events / VI: Định nghĩa events
        Event(() => OrderSubmittedEvent, x => x.CorrelateById(m => m.Message.OrderId));
        Event(() => InventoryReservedEvent, x => x.CorrelateById(m => m.Message.OrderId));
        Event(() => InventoryFailedEvent, x => x.CorrelateById(m => m.Message.OrderId));
        Event(() => PaymentProcessedEvent, x => x.CorrelateById(m => m.Message.OrderId));
        Event(() => PaymentFailedEvent, x => x.CorrelateById(m => m.Message.OrderId));

        // EN: Define state transitions / VI: Định nghĩa chuyển trạng thái
        Initially(
            When(OrderSubmittedEvent)
                .Then(context =>
                {
                    context.Saga.OrderId = context.Message.OrderId;
                    context.Saga.UserId = context.Message.UserId;
                    context.Saga.TotalAmount = context.Message.TotalAmount;
                })
                .Publish(context => new ReserveInventory
                {
                    OrderId = context.Saga.OrderId,
                    Items = context.Message.Items
                })
                .TransitionTo(OrderCreated)
        );

        During(OrderCreated,
            When(InventoryReservedEvent)
                .Publish(context => new ProcessPayment
                {
                    OrderId = context.Saga.OrderId,
                    UserId = context.Saga.UserId,
                    Amount = context.Saga.TotalAmount
                })
                .TransitionTo(InventoryReserved),
                
            When(InventoryFailedEvent)
                .Then(context => context.Saga.FailureReason = context.Message.Reason)
                .Publish(context => new CancelOrder
                {
                    OrderId = context.Saga.OrderId,
                    Reason = context.Message.Reason
                })
                .TransitionTo(Failed)
        );

        During(InventoryReserved,
            When(PaymentProcessedEvent)
                .Then(context => context.Saga.PaymentId = context.Message.PaymentId)
                .Publish(context => new CompleteOrder
                {
                    OrderId = context.Saga.OrderId,
                    PaymentId = context.Message.PaymentId
                })
                .TransitionTo(Completed)
                .Finalize(),
                
            When(PaymentFailedEvent)
                .Then(context => context.Saga.FailureReason = context.Message.Reason)
                // EN: Compensating action - release inventory
                // VI: Action bù trừ - giải phóng inventory
                .Publish(context => new ReleaseInventory
                {
                    OrderId = context.Saga.OrderId
                })
                .Publish(context => new CancelOrder
                {
                    OrderId = context.Saga.OrderId,
                    Reason = context.Message.Reason
                })
                .TransitionTo(Failed)
        );

        SetCompletedWhenFinalized();
    }
}
```

### Saga Events / Events của SAGA

```csharp
/// <summary>
/// EN: Events for order saga.
/// VI: Events cho order saga.
/// </summary>

// EN: Trigger events / VI: Events kích hoạt
public record OrderSubmitted
{
    public Guid OrderId { get; init; }
    public string UserId { get; init; } = default!;
    public decimal TotalAmount { get; init; }
    public List<OrderItemInfo> Items { get; init; } = new();
}

// EN: Command messages / VI: Command messages
public record ReserveInventory
{
    public Guid OrderId { get; init; }
    public List<OrderItemInfo> Items { get; init; } = new();
}

public record ProcessPayment
{
    public Guid OrderId { get; init; }
    public string UserId { get; init; } = default!;
    public decimal Amount { get; init; }
}

public record ReleaseInventory  // Compensating
{
    public Guid OrderId { get; init; }
}

public record CancelOrder  // Compensating
{
    public Guid OrderId { get; init; }
    public string Reason { get; init; } = default!;
}

public record CompleteOrder
{
    public Guid OrderId { get; init; }
    public string PaymentId { get; init; } = default!;
}

// EN: Response events / VI: Events phản hồi
public record InventoryReserved
{
    public Guid OrderId { get; init; }
}

public record InventoryReservationFailed
{
    public Guid OrderId { get; init; }
    public string Reason { get; init; } = default!;
}

public record PaymentProcessed
{
    public Guid OrderId { get; init; }
    public string PaymentId { get; init; } = default!;
}

public record PaymentFailed
{
    public Guid OrderId { get; init; }
    public string Reason { get; init; } = default!;
}
```

### Saga Consumer / Consumer của SAGA

```csharp
/// <summary>
/// EN: Inventory service consumer for saga.
/// VI: Consumer của Inventory service cho saga.
/// </summary>
public class ReserveInventoryConsumer : IConsumer<ReserveInventory>
{
    private readonly IInventoryRepository _repository;
    private readonly ILogger<ReserveInventoryConsumer> _logger;

    public ReserveInventoryConsumer(
        IInventoryRepository repository,
        ILogger<ReserveInventoryConsumer> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task Consume(ConsumeContext<ReserveInventory> context)
    {
        var message = context.Message;
        
        try
        {
            foreach (var item in message.Items)
            {
                var success = await _repository.ReserveAsync(
                    item.ProductId, 
                    item.Quantity,
                    message.OrderId,
                    context.CancellationToken);

                if (!success)
                {
                    // EN: Not enough stock, publish failure
                    // VI: Không đủ hàng, publish failure
                    await context.Publish(new InventoryReservationFailed
                    {
                        OrderId = message.OrderId,
                        Reason = $"Insufficient stock for product {item.ProductId}"
                    });
                    return;
                }
            }

            // EN: All items reserved successfully
            // VI: Tất cả items đã được reserve thành công
            await context.Publish(new InventoryReserved
            {
                OrderId = message.OrderId
            });

            _logger.LogInformation("Inventory reserved for order {OrderId}", message.OrderId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to reserve inventory for order {OrderId}", message.OrderId);
            
            await context.Publish(new InventoryReservationFailed
            {
                OrderId = message.OrderId,
                Reason = ex.Message
            });
        }
    }
}

/// <summary>
/// EN: Compensating consumer to release inventory.
/// VI: Consumer bù trừ để giải phóng inventory.
/// </summary>
public class ReleaseInventoryConsumer : IConsumer<ReleaseInventory>
{
    private readonly IInventoryRepository _repository;
    private readonly ILogger<ReleaseInventoryConsumer> _logger;

    public ReleaseInventoryConsumer(
        IInventoryRepository repository,
        ILogger<ReleaseInventoryConsumer> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task Consume(ConsumeContext<ReleaseInventory> context)
    {
        await _repository.ReleaseReservationAsync(
            context.Message.OrderId,
            context.CancellationToken);

        _logger.LogInformation(
            "Released inventory reservation for order {OrderId}",
            context.Message.OrderId);
    }
}
```

## Common Mistakes / Lỗi Thường Gặp

### 1. Missing Compensating Actions

```csharp
// ❌ BAD: No compensation handling
During(InventoryReserved,
    When(PaymentFailedEvent)
        .TransitionTo(Failed)  // Inventory still reserved!
);

// ✅ GOOD: Proper compensation
During(InventoryReserved,
    When(PaymentFailedEvent)
        .Publish(context => new ReleaseInventory
        {
            OrderId = context.Saga.OrderId
        })
        .TransitionTo(Failed)
);
```

### 2. Non-Idempotent Compensations

```csharp
// ❌ BAD: Can fail if called twice
public async Task Consume(ConsumeContext<ReleaseInventory> context)
{
    var reservation = await _repo.GetReservationAsync(context.Message.OrderId);
    await _repo.DeleteAsync(reservation);  // Throws if already deleted
}

// ✅ GOOD: Idempotent compensation
public async Task Consume(ConsumeContext<ReleaseInventory> context)
{
    var reservation = await _repo.GetReservationAsync(context.Message.OrderId);
    if (reservation != null)
    {
        await _repo.DeleteAsync(reservation);
    }
    // Safe to call multiple times
}
```

### 3. No Timeout Handling

```csharp
// ❌ BAD: Saga can hang forever
public OrderSaga()
{
    During(OrderCreated,
        When(InventoryReservedEvent)
            .TransitionTo(InventoryReserved)
    );
    // What if InventoryReservedEvent never arrives?
}

// ✅ GOOD: Add timeout handling
public OrderSaga()
{
    Schedule(() => ReservationTimeout, x => x.ReservationTimeoutToken, 
        s => s.Delay = TimeSpan.FromMinutes(5));

    During(OrderCreated,
        When(InventoryReservedEvent)
            .Unschedule(ReservationTimeout)
            .TransitionTo(InventoryReserved),
            
        When(ReservationTimeout.Received)
            .Then(ctx => ctx.Saga.FailureReason = "Reservation timeout")
            .Publish(ctx => new CancelOrder { OrderId = ctx.Saga.OrderId })
            .TransitionTo(Failed)
    );
}
```

## Quick Reference / Tham Chiếu Nhanh

### Choreography vs Orchestration

| Aspect | Choreography | Orchestration |
|--------|-------------|---------------|
| Coupling | Low | Higher |
| Complexity | Distributed | Centralized |
| Debugging | Hard | Easier |
| Single point of failure | No | Yes (orchestrator) |
| Best for | Simple flows | Complex flows |

### SAGA Design Checklist

| Step | Required |
|------|----------|
| Define all saga steps | ✅ |
| Define compensating action for each step | ✅ |
| Make compensations idempotent | ✅ |
| Add timeout handling | ✅ |
| Log state transitions | ✅ |
| Design for partial failures | ✅ |

## Resources / Tài Nguyên

- [Detailed Examples](./references/REFERENCE.md) - Full code examples
- [Outbox Pattern](../outbox-pattern/SKILL.md) - Reliable event publishing
- [Inter-service Communication](../inter-service-communication/SKILL.md) - MassTransit
- [Event Sourcing](../event-sourcing/SKILL.md) - Event-based persistence
- [Error Handling](../error-handling-patterns/SKILL.md) - Resilience patterns
