dotnet-cqrs/Svrnty.Sample/Sagas/OrderFulfillmentSaga.cs

147 lines
4.8 KiB
C#

using Svrnty.CQRS.Events.Abstractions.Sagas;
namespace Svrnty.Sample.Sagas;
/// <summary>
/// Sample saga demonstrating compensation pattern for order fulfillment.
/// </summary>
/// <remarks>
/// This saga orchestrates:
/// 1. Reserve Inventory
/// 2. Authorize Payment
/// 3. Ship Order
///
/// If any step fails, all completed steps are compensated in reverse order.
/// </remarks>
public sealed class OrderFulfillmentSaga : ISaga
{
public string SagaId { get; set; } = string.Empty;
public string CorrelationId { get; set; } = string.Empty;
public string SagaName { get; set; } = "order-fulfillment";
}
/// <summary>
/// Saga steps for order fulfillment.
/// </summary>
public static class OrderFulfillmentSteps
{
/// <summary>
/// Step 1: Reserve inventory for the order.
/// </summary>
public static async Task ReserveInventoryAsync(ISagaContext context, CancellationToken cancellationToken)
{
var orderId = context.Get<string>("OrderId");
var items = context.Get<List<OrderItem>>("Items") ?? new List<OrderItem>();
Console.WriteLine($"[SAGA] Reserving inventory for order {orderId}...");
// Simulate inventory reservation
await Task.Delay(100, cancellationToken);
var reservationId = Guid.NewGuid().ToString();
context.Set("ReservationId", reservationId);
Console.WriteLine($"[SAGA] Inventory reserved: {reservationId}");
}
/// <summary>
/// Compensation: Release inventory reservation.
/// </summary>
public static async Task CompensateReserveInventoryAsync(ISagaContext context, CancellationToken cancellationToken)
{
var reservationId = context.Get<string>("ReservationId");
Console.WriteLine($"[SAGA] COMPENSATING: Releasing inventory reservation {reservationId}...");
// Simulate inventory release
await Task.Delay(100, cancellationToken);
Console.WriteLine($"[SAGA] COMPENSATING: Inventory released");
}
/// <summary>
/// Step 2: Authorize payment for the order.
/// </summary>
public static async Task AuthorizePaymentAsync(ISagaContext context, CancellationToken cancellationToken)
{
var orderId = context.Get<string>("OrderId");
var amount = context.Get<decimal>("Amount");
Console.WriteLine($"[SAGA] Authorizing payment for order {orderId}: ${amount}...");
// Simulate payment authorization (could fail based on saga data)
await Task.Delay(100, cancellationToken);
var shouldFail = context.Get<bool>("FailPayment");
if (shouldFail)
{
throw new InvalidOperationException("Payment authorization failed: Insufficient funds");
}
var authorizationCode = Guid.NewGuid().ToString();
context.Set("AuthorizationCode", authorizationCode);
Console.WriteLine($"[SAGA] Payment authorized: {authorizationCode}");
}
/// <summary>
/// Compensation: Void payment authorization.
/// </summary>
public static async Task CompensateAuthorizePaymentAsync(ISagaContext context, CancellationToken cancellationToken)
{
var authorizationCode = context.Get<string>("AuthorizationCode");
Console.WriteLine($"[SAGA] COMPENSATING: Voiding payment authorization {authorizationCode}...");
// Simulate payment void
await Task.Delay(100, cancellationToken);
Console.WriteLine($"[SAGA] COMPENSATING: Payment voided");
}
/// <summary>
/// Step 3: Ship the order.
/// </summary>
public static async Task ShipOrderAsync(ISagaContext context, CancellationToken cancellationToken)
{
var orderId = context.Get<string>("OrderId");
var address = context.Get<string>("ShippingAddress");
Console.WriteLine($"[SAGA] Shipping order {orderId} to {address}...");
// Simulate shipping
await Task.Delay(100, cancellationToken);
var trackingNumber = $"TRACK-{Guid.NewGuid().ToString()[..8].ToUpper()}";
context.Set("TrackingNumber", trackingNumber);
Console.WriteLine($"[SAGA] Order shipped: Tracking #{trackingNumber}");
}
/// <summary>
/// Compensation: Cancel shipment.
/// </summary>
public static async Task CompensateShipOrderAsync(ISagaContext context, CancellationToken cancellationToken)
{
var trackingNumber = context.Get<string>("TrackingNumber");
Console.WriteLine($"[SAGA] COMPENSATING: Canceling shipment {trackingNumber}...");
// Simulate shipment cancellation
await Task.Delay(100, cancellationToken);
Console.WriteLine($"[SAGA] COMPENSATING: Shipment cancelled");
}
}
/// <summary>
/// Order item for saga.
/// </summary>
public sealed class OrderItem
{
public required string ProductId { get; init; }
public required string ProductName { get; init; }
public int Quantity { get; init; }
public decimal Price { get; init; }
}