saga_state_machine

pub package License: MIT

A MassTransit-style saga state machine framework for Dart and Flutter. This package brings the powerful, declarative state machine pattern from MassTransit to the Dart ecosystem.

What is a Saga State Machine?

A saga is a long-running process that coordinates multiple events over time. Unlike simple state machines, sagas:

  • Persist state across multiple events (order processing, call management, workflows)
  • Correlate events to the correct instance using a unique identifier
  • Handle timeouts automatically when states exceed time limits
  • Support compensation (rollback) when things go wrong

This pattern is widely used in distributed systems, microservices, and event-driven architectures. MassTransit popularized this approach in the .NET ecosystem, and saga_state_machine brings the same declarative API to Dart.

Features

  • Declarative API: Define state machines in a readable, self-documenting way
  • Event Correlation: Automatically route events to the correct saga instance by ID
  • Fluent Builder: Chain methods like .set(), .transitionTo(), .finalize()
  • Activities: Execute side effects with optional compensation (rollback)
  • Timeouts: Built-in timeout handling for states with automatic transitions
  • Finalization: Clean up resources when saga completes
  • Pluggable Storage: Default in-memory repository, easily replaceable with custom persistence

Installation

dependencies:
  saga_state_machine: ^1.0.0

Quick Start

Define your Saga

enum OrderStatus { pending, paid, shipped, delivered, cancelled }

class OrderSaga extends Saga {
  String? customerId;
  double amount = 0;
  OrderStatus status = OrderStatus.pending;
}

Define Events

class OrderCreated {
  final String orderId;
  final String customerId;
  final double amount;
  OrderCreated(this.orderId, this.customerId, this.amount);
}

class PaymentReceived {
  final String orderId;
  PaymentReceived(this.orderId);
}

Create the State Machine

class OrderStateMachine extends SagaStateMachine<OrderSaga, OrderStatus> {
  OrderStateMachine() {
    // Correlate events to sagas
    correlate<OrderCreated>((e) => e.orderId);
    correlate<PaymentReceived>((e) => e.orderId);

    // Initial state
    initially(
      when<OrderCreated>()
        .set((saga, e) => saga
          ..customerId = e.customerId
          ..amount = e.amount)
        .transitionTo(OrderStatus.pending),
    );

    // State handlers
    during(OrderStatus.pending,
      when<PaymentReceived>().transitionTo(OrderStatus.paid),
      timeout(Duration(hours: 24), transitionTo: OrderStatus.cancelled),
    );
  }

  @override
  OrderSaga createSaga(String id) => OrderSaga()..id = id;

  @override
  OrderStatus getState(OrderSaga saga) => saga.status;

  @override
  void setState(OrderSaga saga, OrderStatus state) => saga.status = state;
}

Use It

final machine = OrderStateMachine();

// Dispatch events
await machine.dispatch(OrderCreated('order-1', 'customer-1', 99.99));
await machine.dispatch(PaymentReceived('order-1'));

// Query saga
final order = machine.getSaga('order-1');
print(order?.status); // OrderStatus.paid

API Reference

State Machine Methods

Method Description
correlate<E>((e) => id) Define event correlation
initially(when<E>()...) Handle new saga creation
during(state, when<E>()...) Handle events in specific state
duringAny(when<E>()...) Handle events in any state
timeout(duration, transitionTo:) State timeout
whenFinalized(execute:) Cleanup on saga completion
onAnyTransition(callback) Listen to all transitions

Event Handler Methods

Method Description
when<E>() Create handler for event type
.where((e) => bool) Filter events
.set((saga, e) => ...) Set saga properties
.then((ctx) => ...) Execute custom action
.execute(Activity) Execute activity
.transitionTo(state) Transition to state
.finalize() Mark saga as complete
.schedule<E>(duration) Schedule delayed event
.unschedule<E>() Cancel scheduled event

Comparison with MassTransit

This package is designed to mirror the MassTransit saga state machine API as closely as possible in Dart:

Feature MassTransit (C#) saga_state_machine
Declarative DSL Initially(), During() initially(), during()
Event correlation CorrelateById() correlate<E>()
State transitions TransitionTo() .transitionTo()
Timeouts Schedule(), Unschedule() .schedule(), .unschedule()
Activities Activity<T> Activity<TSaga, TEvent>
Compensation Compensate() compensate()
Finalization Finalize() .finalize()
Any state handlers DuringAny() duringAny()
Persistence RabbitMQ, SQL, Redis, MongoDB In-memory (pluggable interface)

MassTransit C# Example

public class OrderStateMachine : MassTransitStateMachine<OrderState>
{
    public OrderStateMachine()
    {
        Initially(
            When(OrderSubmitted)
                .Then(context => context.Saga.CustomerId = context.Message.CustomerId)
                .TransitionTo(Submitted));

        During(Submitted,
            When(PaymentReceived)
                .TransitionTo(Paid),
            When(OrderCancelled)
                .TransitionTo(Cancelled)
                .Finalize());
    }
}

Equivalent saga_state_machine Dart Code

class OrderStateMachine extends SagaStateMachine<OrderSaga, OrderStatus> {
  OrderStateMachine() {
    initially(
      when<OrderSubmitted>()
        .set((saga, e) => saga.customerId = e.customerId)
        .transitionTo(OrderStatus.submitted));

    during(OrderStatus.submitted,
      when<PaymentReceived>().transitionTo(OrderStatus.paid),
      [when<OrderCancelled>().transitionTo(OrderStatus.cancelled).finalize()]);
  }
}

Use Cases

  • Order Processing: Track orders through submitted → paid → shipped → delivered
  • VoIP Call Management: Manage call states (ringing, answered, on hold, completed)
  • Booking Systems: Handle reservations with timeouts and cancellations
  • Workflow Orchestration: Coordinate multi-step business processes
  • IoT Device States: Track device lifecycle and connectivity states

Advanced Features

Custom Activities

class SendEmailActivity extends Activity<OrderSaga, OrderCompleted> {
  @override
  Future<void> execute(BehaviorContext<OrderSaga, OrderCompleted> context) async {
    await emailService.sendOrderConfirmation(context.saga.email);
  }

  @override
  Future<void> compensate(BehaviorContext<OrderSaga, OrderCompleted> context) async {
    await emailService.sendOrderCancellation(context.saga.email);
  }
}

Custom Repository

class PostgresSagaRepository extends SagaRepository<OrderSaga> {
  @override
  OrderSaga? getById(String id) => // fetch from database

  @override
  void save(OrderSaga saga) => // persist to database
}

// Use it
machine.useRepository(PostgresSagaRepository());

Inspiration & Attribution

This package is directly inspired by MassTransit's excellent saga state machine implementation for .NET, created by Chris Patterson. The API design closely follows MassTransit's patterns to provide a familiar experience for developers coming from the .NET ecosystem.

MassTransit is a trademark of Chris Patterson. This package is not affiliated with or endorsed by MassTransit.

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

License

MIT License - see LICENSE for details.

Libraries

saga_state_machine
A MassTransit-style state machine framework for Dart.