dotnet-cqrs/docs/event-streaming/sagas/README.md

3.7 KiB

Sagas

Long-running workflows with compensation logic for distributed transactions.

Overview

Sagas coordinate multiple steps in a workflow, with compensation logic to handle failures. They listen to events, execute business logic, and publish new events to continue the workflow.

Key Features:

  • ISaga - Interface for saga implementations
  • Multi-Step Workflows - Coordinate complex processes
  • Compensation - Rollback on failures
  • State Management - Track saga progress
  • Event-Driven - React to domain events

Quick Start

public class OrderFulfillmentSaga : ISaga
{
    private readonly IEventStreamStore _eventStore;
    private readonly IInventoryService _inventoryService;
    private readonly IPaymentService _paymentService;
    private readonly IShippingService _shippingService;

    public async Task HandleAsync(OrderPlacedEvent @event, CancellationToken ct)
    {
        try
        {
            // Step 1: Reserve inventory
            await _inventoryService.ReserveAsync(@event.OrderId, @event.Items);
            await PublishEventAsync(new InventoryReservedEvent { OrderId = @event.OrderId });

            // Step 2: Process payment
            await _paymentService.ChargeAsync(@event.OrderId, @event.TotalAmount);
            await PublishEventAsync(new PaymentProcessedEvent { OrderId = @event.OrderId });

            // Step 3: Ship order
            await _shippingService.ShipAsync(@event.OrderId);
            await PublishEventAsync(new OrderShippedEvent { OrderId = @event.OrderId });
        }
        catch (Exception ex)
        {
            // Compensation: Rollback
            await CompensateAsync(@event.OrderId);
            await PublishEventAsync(new OrderFailedEvent
            {
                OrderId = @event.OrderId,
                Reason = ex.Message
            });
        }
    }

    private async Task CompensateAsync(int orderId)
    {
        // Release inventory
        await _inventoryService.ReleaseAsync(orderId);

        // Refund payment
        await _paymentService.RefundAsync(orderId);
    }

    private async Task PublishEventAsync(object @event)
    {
        await _eventStore.AppendAsync("orders", new[] { @event });
    }
}

Saga Pattern

OrderPlacedEvent
    ↓
Reserve Inventory
    ↓
InventoryReservedEvent
    ↓
Process Payment
    ↓
PaymentProcessedEvent
    ↓
Ship Order
    ↓
OrderShippedEvent

(If any step fails → Compensate all previous steps)

Features

Saga Pattern

Understand the saga pattern and when to use it.

Creating Sagas

Implement ISaga for long-running workflows.

Compensation

Handle failures with rollback logic.

Common Use Cases

Order Fulfillment:

Place Order → Reserve Inventory → Charge Payment → Ship Order
(Compensation: Release Inventory → Refund Payment)

User Registration:

Register → Send Verification Email → Wait for Confirmation → Activate Account
(Compensation: Delete User → Cancel Email)

Booking System:

Book Flight → Reserve Hotel → Charge Payment → Send Confirmation
(Compensation: Cancel Flight → Cancel Hotel → Refund Payment)

Best Practices

DO

  • Implement compensation for each step
  • Use idempotent operations
  • Store saga state
  • Monitor saga completion
  • Test failure scenarios

DON'T

  • Don't forget compensation logic
  • Don't assume steps always succeed
  • Don't skip state persistence
  • Don't create circular dependencies

See Also