Finite State Machine (FSM) with Akka Actor

Amit Prasad
3 min readApr 16, 2021

A finite state machine (sometimes called a finite state automaton) is a computation model that can be implemented with hardware or software and can be used to simulate sequential logic and some computer programs, which brings a lot of possibilities to think the state and action in terms of natural language.

When we say FSM , two mandatory attributes are attached with this terminology to achieve any computational goal are State and Data.

State and Data are often used for transition from one state to another with the help of Event capture. Event is the wrapper that always expects State and Data to capture and perform the action to change the state along with the data.

Akka has FSM Actor ( is eventually a trait in scala) which expects two parameters State and Data.

Let’s try to understand how FSM can be implemented using Akka Actor in Scala.

State and Data for FSM actor

class VendingMachineFSM extends FSM[VendingState, VendingData] { }

The above snippet means the Vending FSM from the VendingState and the data that is shared between these various States is just VendingData.

Now Since we have the VendingState trait and VendingData, Let’s look into their structure

trait VendingState
case object Idle extends VendingState
case object Operational extends VendingState
case object WaitForMoney extends VendingState

Similarly for VendingData

trait VendingData
case object Uninitialized extends VendingData
case class Initialized(inventory: Map[String, Int], prices: Map[String, Int]) extends VendingDatacase class WaitForMoneyData(inventory: Map[String, Int], prices: Map[String, Int], product: String, money: Int, requester: ActorRef) extends VendingData

So, as we see in the above structure, we have three States — Idle, Operational and WaitForMoney. Our data, the VendingData holds Uninitialized in the beginning, so when the machine starts from dispensing the order it will the preliminary data as Uninitialized along with state Idle which we will see in the example shortly, right after that we can send the event Event(Initialize(inventory, prices), Uninitialized) in the idle state block, after that, it will go to Operational where event Event(RequestProduct(product), Initialized(inventory, prices)) gets capture and then WaitForMoney state.

Of course, after all these, we will have three more important partial function for the entire state transition and operations whenUnhandled,onTransition,initialize()

Now let’s see how to capture each state in when partial function.

class VendingMachineFSM extends FSM[VendingState, VendingData] {

startWith(Idle, Uninitialized)

when(Idle) {
... ...
}

when(Operational) {
...
...
}

when(WaitForMoney, stateTimeout = 1 second) {
...
... }

whenUnhandled {
...
...
stay()
}

onTransition {
case stateA -> stateB =>
//FSM
log.info(s"Transitioning from $stateA to $stateB")
}

initialize()
}

We have an initial State (which is Idle) and any messages that are being sent to the Machine during Idle The state is handled in the when(Idle) block, Operational the state is handled in when(Operational) block, and so on. The messages that I am referring to here are just like regular messages that we tell a plain Actor, except that in the case of FSMs, the message is wrapped along with the Data as well. The wrapper is called an Event (akka.actor.FSM.Event) and an example would look like the case Event(Initialize(inventory, prices), Uninitialized), also at any point of time when event is not satisfied and we want the user to re-attempt the action, we can use stay() method and wait to make a correction to take it forward from operational state.

For event from Akka Documentation

/**
* All messages sent to the [[akka.actor.FSM]] will be wrapped inside an
* `Event`, which allows pattern matching to extract both state and data.
*/
case class Event[D](event: Any, stateData: D) extends NoSerializationVerificationNeeded

We also notice that the when the function accepts two mandatory parameters - the first being the name of the State itself, eg. Idle, Operational etc and the second argument is a PartialFunction, just like an Actor's receive where we do pattern matching. The most important thing to note here is that each of these pattern matching case blocks must return a State (more on this in the next post). So, the code block would look something like

when(Idle) {
case Event(Initialize(inventory, prices), Uninitialized) =>
goto(Operational) using Initialized(inventory, prices)
// equivalent with context.become(operational(inventory, prices))
case _ =>
sender() ! VendingError("MachineNotInitialized")
stay()
}

If there is no matching pattern, then the FSM Actor tries to match our message to a pattern declared in the whenUnhandled block. Ideally, all the messages that are common across all the States is coded away in the whenUnhandled.

Finally, there is an onTransition function which allows you to react or get notified of changes in States, mostly this is used for logging and even we can create another FSM based on individual state capture.

Full code we can get it here, Happy Learning :)

--

--

Amit Prasad

Engineer by profession, Scala | Data engineering | Distributed System Linkedin: https://www.linkedin.com/in/amitprasad119/