using Svrnty.CQRS.Events.Abstractions.Sagas; namespace Svrnty.Sample.Sagas; /// /// Sample saga demonstrating compensation pattern for order fulfillment. /// /// /// This saga orchestrates: /// 1. Reserve Inventory /// 2. Authorize Payment /// 3. Ship Order /// /// If any step fails, all completed steps are compensated in reverse order. /// 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"; } /// /// Saga steps for order fulfillment. /// public static class OrderFulfillmentSteps { /// /// Step 1: Reserve inventory for the order. /// public static async Task ReserveInventoryAsync(ISagaContext context, CancellationToken cancellationToken) { var orderId = context.Get("OrderId"); var items = context.Get>("Items") ?? new List(); 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}"); } /// /// Compensation: Release inventory reservation. /// public static async Task CompensateReserveInventoryAsync(ISagaContext context, CancellationToken cancellationToken) { var reservationId = context.Get("ReservationId"); Console.WriteLine($"[SAGA] COMPENSATING: Releasing inventory reservation {reservationId}..."); // Simulate inventory release await Task.Delay(100, cancellationToken); Console.WriteLine($"[SAGA] COMPENSATING: Inventory released"); } /// /// Step 2: Authorize payment for the order. /// public static async Task AuthorizePaymentAsync(ISagaContext context, CancellationToken cancellationToken) { var orderId = context.Get("OrderId"); var amount = context.Get("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("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}"); } /// /// Compensation: Void payment authorization. /// public static async Task CompensateAuthorizePaymentAsync(ISagaContext context, CancellationToken cancellationToken) { var authorizationCode = context.Get("AuthorizationCode"); Console.WriteLine($"[SAGA] COMPENSATING: Voiding payment authorization {authorizationCode}..."); // Simulate payment void await Task.Delay(100, cancellationToken); Console.WriteLine($"[SAGA] COMPENSATING: Payment voided"); } /// /// Step 3: Ship the order. /// public static async Task ShipOrderAsync(ISagaContext context, CancellationToken cancellationToken) { var orderId = context.Get("OrderId"); var address = context.Get("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}"); } /// /// Compensation: Cancel shipment. /// public static async Task CompensateShipOrderAsync(ISagaContext context, CancellationToken cancellationToken) { var trackingNumber = context.Get("TrackingNumber"); Console.WriteLine($"[SAGA] COMPENSATING: Canceling shipment {trackingNumber}..."); // Simulate shipment cancellation await Task.Delay(100, cancellationToken); Console.WriteLine($"[SAGA] COMPENSATING: Shipment cancelled"); } } /// /// Order item for saga. /// 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; } }