Sagas
EventFlow provides a simple saga system to coordinate messages between bounded contexts and aggregates.
- Saga identity
- Saga
- Saga locator
- Zero or more aggregates
This example is based on the chapter "A Saga on Sagas" from the CQRS Journey by Microsoft, in which we want to model the process of placing an order.
- User sends command
PlaceOrderto theOrderAggregate OrderAggregateemits anOrderCreatedeventOrderSagahandlesOrderCreatedby sending aMakeReservationcommand to theReservationAggregateReservationAggregateemits aSeatsReservedeventOrderSagahandlesSeatsReservedby sending aMakePaymentcommand to thePaymentAggregatePaymentAggregateemits aPaymentAcceptedeventOrderSagahandlesPaymentAcceptedby emitting aOrderConfirmedevent with all the details, which via subscribers updates the user, theOrderAggregateand theReservationAggregate
Next, we need an ISagaLocator which basically maps domain events to a
saga identity allowing EventFlow to find it in its store.
In our case, we will add the order ID to the event metadata of all events related to a specific order.
1 2 3 4 5 6 7 8 9 10 11 12 | |
Alternatively, the order identity could be added to every domain event
emitted from the OrderAggregate, ReservationAggregate, and
PaymentAggregate aggregates that the OrderSaga subscribes to,
but this would depend on whether or not the order identity is part of
the ubiquitous language for your domain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Attention
Even though the method for publishing commands is named
Publish, the commands are only published to the command bus
after the aggregate has been successfully committed to the event
store (just like events). If an unexpected exception is thrown by this
command publish, it should be handled by a custom implementation of
ISagaErrorHandler.
The next few events and commands are omitted in this example, but at last the
PaymentAggregate emits its PaymentAccepted event and the saga
completes and emits the final OrderConfirmed event.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Note
An AggregateSaga<,,> is only considered in its running
state if there has been an event and it hasn't been marked as completed
(by invoking the protected Complete() method on the
AggregateSaga<,,>).
Understanding Saga Lifecycle States
Each Saga has an internal State property that defines how it processes events. This property can have the following values:
- New
- Only events defined using
ISagaIsStartedBy<>can be processed. - Running
- Events defined using
ISagaHandles<>will be processed. - Events defined using
ISagaIsStartedBy<>will also behave the same asISagaHandles<>. - Completed
- No events will be processed by the Saga anymore.
Alternative saga store
By default, EventFlow is configured to use event sourcing and aggregate
roots for the storage of sagas. However, you can implement your own storage
system by implementing ISagaStore and registering it.