144 lines
3.7 KiB
Markdown
144 lines
3.7 KiB
Markdown
# 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
|
|
|
|
```csharp
|
|
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](saga-pattern.md)
|
|
Understand the saga pattern and when to use it.
|
|
|
|
### [Creating Sagas](creating-sagas.md)
|
|
Implement ISaga for long-running workflows.
|
|
|
|
### [Compensation](compensation.md)
|
|
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
|
|
|
|
- [Event Streaming Overview](../README.md)
|
|
- [Events and Workflows](../fundamentals/events-and-workflows.md)
|
|
- [Projections](../projections/README.md)
|