I am trying to leverage Akka's finite state machine framework for my use case. I am working on a system that processes a request that goes through various states.
The request here is the application name that needs to be deployed along with the application it depends on:
Request for application A -> A is in a QUEUED state Discover A's dependency B -> B is in a QUEUED state B is being processed -> B is in a PROCESSING STATE A is being processed -> A is in a PROCESSING STATE B is processed -> B is in a DONE state A is processed -> A is in a DONE state
For this I am initializing a finite state machine at discovery time. So A
's FSM is created when the request comes in, B
's FSM is initialized when B
is discovered from one of the actors.
Do I initialize and pass the FSM instance to all the actors, and at the same time tell
the FSM about the operations being performed on the data so that the state machine goes in the correct state?
Here is the relevant portion of the state machine:
when(QUEUED, matchEvent(requestAccepted.class, MyApp.class, (requestAccepted, service) -> goTo(PROCESSING).replying(PROCESSING))); when(PROCESSING, matchEvent(completed.class, MyApp.class, (completed, service) -> goTo(DONE).replying(DONE))); // During transitions, save states in the database. onTransition(matchState(PROCESSING, DONE, () -> { nextStateData().setServiceStatus(DONE); databaseWriter.tell(nextStateData(), getSelf());
And here is an example from one of the actors processing the request:
ProcessingActor extends AbstractActor { @Override public void onReceive(Object message) throws Throwable { if (message instanceof processApplication) { // process the app // Initialize FSM for the Application FSM myFSM = Props.create(MYFSM.class); myFSM.tell( new completed(processApplication.app) }
Is this the right way to initialize the state machine and use it? Or should the initialization happen in the constructor of the ProcessingActor
? But in that case there wouldn't be one state machine per application (data).
The FSM (Finite State Machine) is available as a mixin for the an abstract base class that implements an Akka Actor and is best described in the Erlang design principles. A FSM can be described as a set of relations of the form: State(S) x Event(E) -> Actions (A), State(S')
An actor can be used to model a Finite State Machine (FSM). To demonstrate this, consider an actor which shall receive and queue messages while they arrive in a burst and send them on after the burst ended or a flush request is received.
In Akka, actors are guaranteed to be run in a single-threaded illusion, which means that the Akka framework takes care of threading issues while allowing us to focus on the behavior that needs to be implemented. Actors may only communicate with each other and the outside world by through messages.
Akka gives developers a unified way to build scalable and fault-tolerant software that can scale up on multicore systems, and scale out in distributed computing environments, which today often means in the cloud.
TL;DR; While the description of states and transitions is rather ambiguous in the OP, the status of the chosen implementation, its implications and those of alternatives can be addressed.
The implementation at hand can count as an "actor per request" approach, as opposed to what more frequently can be found, where the state-machine actor buffers and handles multiple requests. It will be presented further below.
In its present form the given implementation is the only AbstractFSM per request I know of, actually with yet another FSM downstream, which then can't be called "per request" any more and likely could be avoided. It looks roughly like this:
The "actor per request" initially seems to have come up in this discussion, which gave raise to this blog and a bit later even occasionally claimed the title of a pattern. It seems in part it was motivated by replicating one of the features of the Spray framework, sort of precursor of Akka http.
Another discussion on SO came to an inconclusive result on the question whether to prefer a single actor over one per request, and presents routing as a 3rd alternative, because a router also acts as a load balancer, touching the back-pressure topic.
The approach more frequently presented in combination with AbstractFSM
and its varieties, though, is a single FSM actor that buffers incoming messages utilizing the whenUnhandled
method to accumulate them by default, no matter what the current state is (e.g. Akka in Action, chapter "Finite State Machines and Agents" or the Lightbend buncher example). I'm not able to back up the claim with a reference, but it looks like AbstractFSM is more thought of to model the states of an actor that deals with multiple requests, rather than that of a request that goes through multiple stages. Related to the OP that approach would mean, that ProcessingActor
itself could extend AbstractFSM
.
public class ProcessingActor extends AbstractFSM<sample.so.State, RequestQueue> { { startWith(Idle, new RequestQueue()); when(Idle, /* matchEvent(EventType, DataType, Action) */ matchEvent( Request.class, RequestQueue.class, (request, queue) -> goTo(Processing).using(queue.push(request))); /* more state-matchers */ whenUnhandled( matchEvent( Request.class, RequestQueue.class, (request, qeue) -> stay().using(qeue.push(request))); initialize(); }
Arriving at something like this for the "request"-part, where the database writes aren't represented by states any more, rather than state entry and exit actions. Note that all the whenUnhandled
branch doesn't appear in the diagram, since it isn't related to a state change.
Without going too much into weighing (vague) requirements against the chosen implementation, AbstractFSM
seems to be a rather clumsy machinery to log a sequence of status per request to a database. The documentation of the 2.4 version of Akka presents a state machine without using AbstractFsm and discusses the possibility to distinguish first events and then states or the other way round. In the AbstractFSM
-DSL you're bound to discern states before events (and data). It can even be argued with some justification to abstain from the akka FSM altogether.
A more light-weight approach to build FSM utilizes become/unbecome, a nice presentation is here, an AbstractFSM
vs. become/unbecome discussion can be found here. Closer visual proximity to business rules gives the major argument for the former.
Entering the more slippery terrain of judging the use of AbstractFSM for the task at had. I think, some things can be said, given the reading of the requirements is approximately ok.
The states form a linear chain, so the two "AbstractFSM-per-request" can be replaced by other per-request structures:
The appeal of those versions may increase considering this: since the given implementation uses (at least) one FSM per request, the question arises, how the transition QUEUED->PROCESSING
comes about (or discovery processing -> discovery done
for that matter), and what QUEUED
relates to a technical level. Items are never in a queue, always in an exclusive actor (on the other hand a QUEUED state would be more obvious in a single-FSM approach which actually uses a queue, but then the state doesn't apply to the actor, but to the item the actor handles). It is not obvious, which external event should cause that transition. Akka FSM is geared to describe deterministic FSM (see also this, giving the same argument for the opposite reasons), but if this transition is not triggered by an external event, the FSM must become nondeterministic and trigger its own epsilon-transitions ~ transitions not caused by any input. A rather complicated structure, probably implemented somehow like this:
onTransition( matchState(Initial, Queued, () -> { getSelf().tell(new Epsilon(), getSelf()); }));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With