EventSourced behaviors as finite state machines
An EventSourcedBehavior
can be used to represent a persistent FSM. If you’re migrating an existing classic persistent FSM to EventSourcedBehavior see the migration guide.
To demonstrate this consider an example of a shopping application. A customer can be in the following states:
- Looking around
- Shopping (has something in their basket)
- Inactive
- Paid
- Scala
-
source
sealed trait State case class LookingAround(cart: ShoppingCart) extends State case class Shopping(cart: ShoppingCart) extends State case class Inactive(cart: ShoppingCart) extends State case class Paid(cart: ShoppingCart) extends State
- Java
And the commands that can result in state changes:
- Add item
- Buy
- Leave
- Timeout (internal command to discard abandoned purchases)
And the following read only commands:
- Get current cart
- Scala
-
source
sealed trait Command case class AddItem(item: Item) extends Command case object Buy extends Command case object Leave extends Command case class GetCurrentCart(replyTo: ActorRef[ShoppingCart]) extends Command private case object Timeout extends Command
- Java
The command handler of the EventSourcedBehavior is used to convert the commands that change the state of the FSM to events, and reply to commands.
The command handler:
- Scala
-
source
def commandHandler(timers: TimerScheduler[Command])(state: State, command: Command): Effect[DomainEvent, State] = state match { case LookingAround(cart) => command match { case AddItem(item) => Effect.persist(ItemAdded(item)).thenRun(_ => timers.startSingleTimer(StateTimeout, Timeout, 1.second)) case GetCurrentCart(replyTo) => replyTo ! cart Effect.none case _ => Effect.none } case Shopping(cart) => command match { case AddItem(item) => Effect.persist(ItemAdded(item)).thenRun(_ => timers.startSingleTimer(StateTimeout, Timeout, 1.second)) case Buy => Effect.persist(OrderExecuted).thenRun(_ => timers.cancel(StateTimeout)) case Leave => Effect.persist(OrderDiscarded).thenStop() case GetCurrentCart(replyTo) => replyTo ! cart Effect.none case Timeout => Effect.persist(CustomerInactive) } case Inactive(_) => command match { case AddItem(item) => Effect.persist(ItemAdded(item)).thenRun(_ => timers.startSingleTimer(StateTimeout, Timeout, 1.second)) case Timeout => Effect.persist(OrderDiscarded) case _ => Effect.none } case Paid(cart) => command match { case Leave => Effect.stop() case GetCurrentCart(replyTo) => replyTo ! cart Effect.none case _ => Effect.none } }
- Java
The event handler is used to change state once the events have been persisted. When the EventSourcedBehavior is restarted the events are replayed to get back into the correct state.
- Scala
-
source
def eventHandler(state: State, event: DomainEvent): State = { state match { case la @ LookingAround(cart) => event match { case ItemAdded(item) => Shopping(cart.addItem(item)) case _ => la } case Shopping(cart) => event match { case ItemAdded(item) => Shopping(cart.addItem(item)) case OrderExecuted => Paid(cart) case OrderDiscarded => state // will be stopped case CustomerInactive => Inactive(cart) } case i @ Inactive(cart) => event match { case ItemAdded(item) => Shopping(cart.addItem(item)) case OrderDiscarded => i // will be stopped case _ => i } case Paid(_) => state // no events after paid } }
- Java