Workflow Foundation  

Building Multi-Step Workflows and Sagas in .NET

Large enterprise systems rarely perform tasks in a single step. Real business processes, such as order fulfilment, inventory updates, user onboarding, payment processing, and approval chains, involve multiple steps that must be executed reliably. These processes become more complex when they span multiple microservices, databases, or external systems.

In such scenarios, developers need a consistent way to manage long-running operations, coordinate interdependent tasks, and handle partial failures gracefully. This is where multi-step workflows and the Saga pattern become essential. In the .NET ecosystem, these patterns can be implemented using built-in features, libraries, and best practices.

This article explains the core concepts and shows how to build workflows and sagas effectively in .NET.

Understanding Multi-Step Workflows

A workflow is a sequence of steps that must be executed in a defined order to complete a business process. Each step may involve data validation, database changes, service calls, or background tasks.

Common examples of multi-step workflows:

  • Creating a purchase order, validating it, applying approval rules, and updating inventory.

  • Registering a new user, sending verification email, creating a profile, and generating default settings.

  • Processing an online payment, confirming the transaction, updating the ledger, and notifying the customer.

In .NET, workflows can be implemented in different ways depending on the complexity:

  • Simple in-memory orchestrations using application services.

  • Background workflows using Hangfire, Quartz.NET, or Worker Service.

  • Distributed workflows using Durable Functions or MassTransit State Machines.

What Is the Saga Pattern?

A Saga is a design pattern used to manage long-running, multi-step transactions in distributed systems. Instead of using a single atomic transaction, each step in a saga executes independently. If something fails, the system runs defined compensating actions to undo or correct previous steps.

Why Sagas Are Needed

Traditional transactions (ACID) only work within a single database. Enterprise applications often work across:

  • Multiple microservices

  • Different databases

  • External partners (UPS, payment gateways, etc.)

  • Cloud services such as storage or messaging

In such cases, a failure in one step should not leave the entire system in an inconsistent state. Sagas help in:

  • Maintaining data consistency

  • Handling partial failures

  • Implementing rollback logic safely

  • Managing long-running workflows

Types of Sagas

There are two common saga approaches:

1. Orchestration-Based Saga

A central orchestrator controls the entire workflow:

  • Calls each service step-by-step

  • Waits for results

  • Triggers compensating actions on failures

This is easier to manage and debug, and is suitable for .NET applications using libraries like MassTransit or Durable Functions.

2. Choreography-Based Saga

There is no central coordinator. Each service:

  • Performs its step

  • Publishes an event

  • Other services react to that event and continue the workflow

This approach is suitable for event-driven systems using Azure Service Bus, RabbitMQ, or Kafka.

Implementing Multi-Step Workflows in .NET

1. Using Application Services (Simple Workflows)

This method works for simple workflows within a single application.

public async Task PlaceOrderAsync(OrderRequest request)
{
    var order = await _orderRepository.CreateAsync(request);
    await _paymentService.ChargeAsync(order);
    await _inventoryService.ReserveStockAsync(order);
    await _notificationService.SendConfirmation(order);
}

This is easy, but failures require manual compensation logic.

2. Using MassTransit State Machines (Saga Implementation)

MassTransit is a widely used library in .NET for building sagas and distributed workflows.

Example Saga Workflow: Order Processing

public class OrderState : SagaStateMachineInstance
{
    public Guid CorrelationId { get; set; }
    public string CurrentState { get; set; }
    public Guid OrderId { get; set; }
}

Saga State Machine

public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
    public State Submitted { get; private set; }
    public Event<OrderSubmitted> OrderSubmitted { get; private set; }

    public OrderStateMachine()
    {
        InstanceState(x => x.CurrentState);

        Event(() => OrderSubmitted, x => x.CorrelateById(m => m.Message.OrderId));

        Initially(
            When(OrderSubmitted)
                .Then(context =>
                {
                    // Call payment service, inventory service, etc.
                })
                .TransitionTo(Submitted)
        );
    }
}

MassTransit handles:

  • State persistence

  • Retry policies

  • Message routing

  • Compensation logic

This is highly suitable for microservice architecture.

3. Using Durable Functions (Orchestrator-Based Workflow)

Durable Functions (Azure Functions) make it very easy to build long-running workflows.

Example Workflow

[FunctionName("OrderOrchestrator")]
public async Task RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var order = context.GetInput<OrderRequest>();

    await context.CallActivityAsync("ValidateOrder", order);
    await context.CallActivityAsync("ChargePayment", order);
    await context.CallActivityAsync("ReserveStock", order);
    await context.CallActivityAsync("SendEmail", order);
}

Durable Functions automatically support:

  • State persistence

  • Retry policies

  • Timeouts

  • Human approval steps

Handling Failures and Compensating Actions

Compensation is the most important part of sagas. For every step, define a reverse operation.

Examples:

  • If payment succeeds but inventory fails → refund the payment.

  • If email confirmation fails → retry or log error without blocking the workflow.

  • If order approval is rejected → release reserved stock.

In MassTransit or Durable Functions, compensation is implemented inside the orchestrator or state machine.

When to Use Multi-Step Workflows and Sagas

Use them when your system has:

  • Distributed transactions across services or databases

  • Long-running operations (minutes to days)

  • Business processes that require multiple approvals

  • Heavy integration with external systems

  • High reliability requirements

Avoid them for simple CRUD operations or synchronous actions.

Best Practices

  • Define each workflow step as an independent, idempotent operation.

  • Use message queues such as Azure Service Bus or RabbitMQ for reliability.

  • Log state transitions clearly for debugging.

  • Prefer asynchronous communication to avoid blocking.

  • Implement retry policies with exponential backoff.

  • Use correlation IDs to track workflow progress.

Conclusion

Building multi-step workflows and sagas in .NET enables developers to create reliable, fault-tolerant, and scalable business processes. Whether you are using ASP.NET Core for enterprise APIs or working with a microservice architecture, understanding these patterns is essential for modern application development.

By using orchestration or choreography along with tools like MassTransit, Durable Functions, and background job frameworks, .NET developers can easily implement consistent long-running workflows while avoiding the complexity of distributed transactions.

If you are working on ERP-level systems, order management, shipping integrations, or approval workflows, sagas and workflow orchestration will significantly improve the stability and maintainability of your application.