I have a question regarding the design of actors which are finite state machines. I’m not using akka-typed
, just regular Akka.
I have a clustered and sharded persistent entity (an aggregate) which is receiving a bunch of messages, which are effectively commands asking the entity to mutate the state of the entity. I would like this actor to then tell other (non-persistent) actors to mutate the state of the aggregate’s child objects. These actors then send the mutated state (if its mutated) back to the persistent entity as its events, which are then persisted.
I want to do it this way because the number and some of the types of children of the aggregate are quite large and complex, and I can isolate the state mutation into classes which only have one concern (rather than big, unwieldy, and synchronous code, all managed by the persistent entity). Some of these input commands may also mutate several different children of the aggregate, so it makes sense to do all this in parallel.
However some of these actors need to be implemented as Finite State Machines. The issue is the “initial” state is held in the aggregate entity; they shouldn’t be started as “Uninitialised”, but in whatever “current” state is held in the aggregate (or the child entity which is sent to the actor).
How do I best achieve this? Consider this simplified design:
class FlightAggregate extends AbstractPersistentActor {
private FlightEntity entity = new FlightEntity();
@Override
public String persistenceId() {
return getSelf().path().name();
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(TransformationSuccessEvent.class,
transformation -> {
applyEvent(transformation)
}
)
.match(TakeOff.class,
tko -> {
for (FlightLeg leg in entity.flightLegs()) {
final ActorRef legActor =
getContext().getSystem().actorOf(TakeOffAndLandActor.props());
legActor.tell(new TakeOffLegState(tko, leg), this.getSelf());
}
})
// Land, and many others here
.build();
}
}
Now, the TakeOffAndLandActor
knows how to take off and land an individual flight leg. There are two considerations:
- Does the
TakeOff
command apply to this leg? - Is the
FlightLeg
in such a state as it can be taken off?
If both of these are true then a new FlightLeg
is computed and returned inside the TransformationSuccessEvent
, which is the event that’s persisted by the FlightAggregate entity.
Now I could just implement all that logic (mostly around item 2.
in the list above) “statically” in the TakeOffAndLandActor
, but it seems to me that for more complex scenarios this will end up with a whole bunch of horrific spaghetti code. now it occurred to me that the flight leg actor could either be an FSM actor, or use subsidiary FSM actors. These states would along the lines of Scheduled
, Boarding
, AircraftReady
, TakenOff
, Landed
, Disembarked
etc. In this case, the valid State to receive a ‘TakeOff’ event would be AircraftReady
.
But I first need to initialise the FSM as being in whatever state is currently held in the FlightLeg
entity. I don’t want the FSM to be a persistent entity that fully tracks all the legs and their states. I just want it encapsulate the nodes and edges of the directed graph which is the FSM (it’s a lot more complex than what I’ve just outlined above, which is why I want to use a state machine to capture it). How do I do that?
Thanks!
Scot