This project demonstrates an implementation of the Saga pattern using .NET 8 and Azure Durable Functions. The Saga pattern is a failure management pattern that helps maintain data consistency across distributed business processes by defining compensating actions for each step in the workflow.
The sample implements an order processing workflow with multiple steps that must either all complete successfully or be rolled back entirely. This ensures data consistency across systems even when failures occur.
Our order processing saga handles a complete e-commerce order scenario:
- Send notification - Inform the customer that order processing is starting
- Reserve inventory - Mark inventory as reserved for this order
- Request approval - Get order approval (may be manual or automated)
- Process payment - Handle payment processing
- Update inventory - Convert reserved inventory to confirmed
- Process delivery - Schedule delivery of the order
If any step fails, the system automatically executes compensation actions in reverse order to maintain consistency.
The key to our implementation is the Compensations class, which:
public class Compensations
{
private readonly List<Func<Task>> _compensations = new();
// Register a compensation action to be executed if the workflow fails
public void AddCompensation<T>(string activityName, T input) { ... }
// Execute all registered compensation actions in reverse order
public async Task CompensateAsync(bool inParallel = false) { ... }
}- Register Compensation First: Always register a compensation action before performing the actual operation
- LIFO Execution Order: Compensations are executed in reverse order of registration
- Error Handling: Failures during compensation don't stop other compensations
- Optional Parallel Execution: Compensations can run in parallel if desired
For each business operation, we define a corresponding compensation:
| Operation | Compensation |
|---|---|
| Reserve Inventory | Release Inventory |
| Process Payment | Refund Payment |
| Update Inventory | Restore Inventory |
- Data Consistency: Ensures all systems remain consistent even during failures
- Clear Separation of Concerns: Main workflow logic is separate from compensation logic
- Reduced Complexity: Eliminates deeply nested try/catch blocks
- Automatic Management: System tracks and executes compensations
- Durability: State is persisted automatically by Durable Functions
- .NET 8 SDK
- Azure Functions Core Tools (v4.x)
- Docker for running the DTS emulator
The sample is configured to use the Durable Task Scheduler as the backend for Durable Functions:
There are two ways to run this sample locally:
The emulator simulates a scheduler and taskhub in a Docker container, making it ideal for development and learning.
-
Pull the Docker Image for the Emulator:
docker pull mcr.microsoft.com/dts/dts-emulator:latest
-
Run the Emulator:
docker run --name dtsemulator -d -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
Wait a few seconds for the container to be ready.
Note: The example code automatically uses the default emulator settings (endpoint: http://localhost:8080, taskhub: default). You don't need to set any environment variables.
-
Update Configuration (if needed): Verify that
local.settings.jsonincludes the Durable Task Scheduler connection:{ "IsEncrypted": false, "Values": { "AzureWebJobsStorage": "UseDevelopmentStorage=true", "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", "DurableTaskSchedulerConnection": "Endpoint=http://localhost:8080;Authentication=None" }, "Host": { "LocalHttpPort": 7071 } }And check that
host.jsonis configured to use the Durable Task Scheduler:{ "version": "2.0", "extensions": { "durableTask": { "hubName": "default", "storageProvider": { "type": "AzureManaged", "connectionStringName": "DurableTaskSchedulerConnection" } } } }
-
Clone the repository:
git clone <repository-url> cd durable-functions/dotnet/Saga -
Start the function app:
func start -
Test the workflow:
- Use the provided
test.httpfile if you have the REST Client extension in VS Code - Or use curl/Postman to make the following requests:
# Start a new order processing workflow POST http://localhost:7071/api/orders Content-Type: application/json { "customerId": "customer123", "productId": "product456", "quantity": 5, "amount": 100.00 } # The response will include an instanceId - copy it for the next request # Check the status GET http://localhost:7071/api/orders/{instanceId} - Use the provided
-
Monitor the logs:
- Watch the console output to see the workflow execution
- In approximately 50% of runs, the delivery step will fail (by design)
- The approval step also fails 25% of the time (by design)
- When it fails, you'll see the compensation actions run in reverse order
- Saga/Compensations.cs - Core compensation management logic
- Models/ - Data models for order, inventory, payment, etc.
- Activities/ - Implementation of business operations and compensations
- Orchestrators/OrderProcessingOrchestrator.cs - The main workflow orchestrator
- Functions/HttpTriggers.cs - API endpoints to interact with the workflow
-
POST /api/orders - Start a new order processing workflow
- Body: Order details (customerId, productId, quantity, amount)
- Returns: Instance ID and status URL
-
GET /api/orders/{instanceId} - Get status of an order processing workflow
- Returns: Current state, status, and output
-
POST /api/orders/{instanceId}/terminate - Cancel a running workflow
- Returns: Confirmation of termination