Why is the type PersistentEntity.CommandHandler private?

I’m using Lagom to model changes in an entity that closely resembles an FSM (I’m sure a common use pattern). Since the FSM has terminal states, after which no change to the entity is possible, what I’d like to do is to stick a “guard action” at the top of the Behaviour chain to prevent any modifications, something like:

override def behavior: Behavior = {
  // Don't do anything to entities in a terminal state
  case Some(entity) if entity.inTerminalState =>
    new TerminalStateActions()
  case Some(entity) =>
    // .. model my entity here

An implementation of TerminalStateActions might be as simple as:

class TerminalStateActions() extends Actions(Map.empty, Map.empty.withDefaultValue{ case (cmd, ctx, st) => 
  ctx.commandFailed(new ModifyEntityInTerminalStateException("Can't modify this entity"))

Tying to implement this myself, I was stymied by the fact that Actions.commandHandlers is a Map[Class[_], CommandHandler] and CommandHandler is private[lagom], meaning I can’t do this without hacks.

So I guess I have two questions:

  1. Is this an anti-pattern that Lagom is deliberately not letting me fall into? If so, what’s the general shape of the pattern?
  2. If that’s not the case, what’s a good way to implement terminal states in an FSM - ones which can’t be exited from - without having to add a no-op command handler every time I add a message?

Many thanks!

Hi @hythloday,

this is an interesting case. If this is a common idiom it may be interesting to add support for terminal states in the framework.

In the meantime, ket me share the approach we’ve used in some samples:

  override def behavior: Behavior = {
    case AuctionState(_, NotStarted, _) => notStarted
    case AuctionState(_, UnderAuction, _) => underAuction
    case AuctionState(_, Complete, _) => complete
    case AuctionState(_, Cancelled, _) => cancelled

In that case, both cancelled and complete are terminal states but they slightly differ.


@hythloday we have a similar pattern in our codebase, and have been similarly frustrated by the private modifer on CommandHandler. In general, it’s astonishingly hard to extract common behavior out of PersistentEntities. #1 pain point with Lagom.