Sagas: Workflows That Remember

Learn how to build long-running workflows that remember state using Sagas

While connecting handlers with channels works great for simple workflows, some business processes need to remember what happened before. This is where Sagas come in.

When Do You Need Sagas?

Use Sagas when your workflow needs to:

🌳 Branch and merge: Split into multiple paths that later combine ⏳ Wait for humans: Pause for manual approval or external actions πŸ“Š Track progress: Monitor long-running processes (hours, days, weeks) πŸ”„ Handle complexity: Make decisions based on accumulated state

Examples:

  • Order processing (payment β†’ shipping β†’ delivery)

  • Loan approval (application β†’ verification β†’ decision)

  • User onboarding (signup β†’ verification β†’ welcome)

Prerequisites: Familiarity with connecting handlers and aggregates will help you understand Sagas better.

Creating Your First Saga

A Saga is like a persistent coordinator that remembers its state between events. Let's build an order processing saga step by step.

Step 1: Define the Saga Structure

#[Saga]
final class OrderProcess
{
    private function __construct(
        #[Identifier] private string $orderId,  // πŸ‘ˆ Unique ID to find this saga
        private OrderStatus $status,            // πŸ‘ˆ Current state
        private bool $isPaid = false,           // πŸ‘ˆ Remember payment status
        private bool $isShipped = false,        // πŸ‘ˆ Remember shipping status
    ) {}
}

Key parts:

  • #[Saga] - Tells Ecotone this is a stateful workflow

  • #[Identifier] - Unique ID to find and update this specific saga instance

  • Private properties - The state that gets remembered between events

Step 2: Start the Saga

Sagas begin when something important happens (usually an event):

What happens:

  1. OrderWasPlaced event occurs

  2. Ecotone creates a new OrderProcess saga instance

  3. The saga is saved to storage (database, etc.)

  4. Now it can react to future events for this order

Storage

Sagas are automatically stored using Repositories. You can use Doctrine ORM, Eloquent, or Ecotone's Document Store - no extra configuration needed!

Step 3: React to Events and Take Actions

Now let's make the saga react to events and coordinate the workflow:

Triggering Commands

When payment succeeds, we want to start shipping:

The flow:

  1. PaymentWasSuccessful event arrives

  2. Saga updates its internal state

  3. Saga returns ShipOrder command

  4. Command goes to shipOrder channel

  5. Shipping handler processes the order

Alternative: Using Command Bus

You can also send commands directly:

Timing difference:

  • outputChannelName: Saga state saved first, then command is sent

  • CommandBus: Command sent first, then saga state saved

Step 4: Publishing Events and Timeouts

Sagas can also publish events to trigger other parts of your system or set up timeouts.

Publishing Events

Setting Up Timeouts

Cancel orders that aren't paid within 24 hours:

Timeline:

  • ⏰ T+0: Order placed, saga starts, timeout scheduled

  • ⏰ T+24h: If still unpaid, automatically cancel

Advanced Patterns

Conditional Event Handling

Sometimes you only want to handle events if the saga already exists. Use dropMessageOnNotFound:

How it works:

  • βœ… If saga exists: Event is processed

  • ❌ If saga doesn't exist: Event is ignored (dropped)

  • 🎯 Use case: Features that depend on previous conditions

Querying Saga State

Expose saga state to your application (great for status pages, dashboards):

Using in your controllers:

Perfect for:

  • Order tracking pages

  • Progress indicators

  • Admin dashboards

  • Customer support tools

Handling Unordered Events

Real-world events don't always arrive in order. Ecotone handles this elegantly with method redirection:

How Ecotone decides:

  • 🎯 Same event, different behavior based on saga state

  • πŸ†• Saga doesn't exist β†’ Calls static factory method

  • βœ… Saga exists β†’ Calls action method

Benefits:

  • No complex if/else logic in your code

  • Handles event ordering issues automatically

  • Clean separation of initialization vs. processing logic

Saga Identification and Correlation

Finding the Right Saga Instance

Every event/command needs to find the correct saga instance. Ecotone uses Identifier Mapping for this:

Using Correlation IDs

For complex workflows that branch and merge, use correlation IDs:

Benefits of correlation IDs:

  • Track workflows across multiple services

  • Handle branching and merging flows

  • Automatic propagation through message chains

Correlation IDs are automatically propagated between messages, making them perfect for complex workflows that span multiple services or branches.

Testing Sagas with Ecotone Lite

Testing sagas is essential for ensuring your stateful workflows behave correctly. Ecotone Lite makes saga testing straightforward and comprehensive.

Setting Up Saga Tests

Testing Saga State Changes

Test how sagas respond to events and update their state:

Summary: When to Use Sagas

βœ… Use Sagas when you need to:

  • Remember state between events

  • Coordinate long-running processes

  • Handle branching/merging workflows

  • Implement timeouts and cancellations

  • Track progress of complex operations

❌ Don't use Sagas for:

Last updated

Was this helpful?