Akka Episode II – The Empire Strikes Back

In the previous post, we learned the basic foundations of the Akka toolkit. In this post, we‘ll see the nuts and bolts of an Akka application built on Scala. For doing this, at first we must define our actor model, and later implement every actor and the necessary messages to communicate actors between them.

The Death Star will be completed on schedule

Using an example is the best way to understand the Actor Model. Let’s imagine in the Star Wars universe a plot where the evil Emperor wants to build the Death Star, which is a big space station with a weapon capable of destroying planets.

For this purpose, we need to define an Actor Model where every actor has a specific role in the task of building the Death Star. Our design includes the following actors:

  • Director: person in charge of managing the complete building. The evil emperor requests the building of the Death Star, and the director should coordinate the building. He will respond a message when the work will be finished.
  • Designer: person responsible for creating the plans to build the Death Star. After this, the actor must deliver the plans to the Director Actor.
  • Miner: person responsible for providing the necessary materials to build the Death Star. This actor needs to know the plans to calculate the measure of materials to extract in the mine. He must deliver the materials to the Director Actor.
  • Worker: person responsible for building the space station, having the plans and the materials. He must assemble the materials according to the plans. Finally the actor will report to the Director actor when the Death Star will be finished.

I saw part of the message he was…

We are going to show the Scala code corresponding to our Actor Model design. Now we know the Actor Model design we can implement the different actors. But before this we have to define the messages for communicating between the actors.

package com.datio.akka.demo
case class RequestBuilding()
case class ResponseBuilding(message: String, status: String = "PENDING", execution: scala.collection.immutable.List[(String, String)])
case class RequestPlans()
case class ResponsePlans(plans: scala.collection.immutable.List[String])
case class RequestMaterials(plans: scala.collection.immutable.List[String])
case class ResponseMaterials(materials: scala.collection.immutable.List[String])
case class RequestWorkerBuilding(plans: scala.collection.immutable.List[String], materials: scala.collection.immutable.List[String])

Everything that has transpired has done so according to my design

Now that we have defined the messages of our Actor System we can implement the first actor. The Director Actor receives the assignment from the evil emperor to build the Death Star. This actor must coordinate the messages between the rest of actors to ensure the different components are managed in the correct order.


package com.datio.akka.demo.actor
import akka.actor.{Actor, ActorLogging, ActorRef, Props}
import com.datio.akka.demo.Constants._
import com.datio.akka.demo._
object DirectorActor {
 def props(): Props = Props(classOf[DirectorActor])
}
/**
 * This actor manages others actors for getting plans and materials
 * before ordering the building of the Death Star.
 */
class DirectorActor extends Actor with ActorLogging {

 val designerActor = context.child(DESIGNER_KEY)
   .getOrElse(context.actorOf(Props(classOf[DesignerActor]), DESIGNER_KEY))
 val minerActor = context.child(MINER_KEY)
   .getOrElse(context.actorOf(Props(classOf[MinerActor]), MINER_KEY))
 val workerActor = context.child(WORKER_KEY)
   .getOrElse(context.actorOf(Props(classOf[WorkerActor]), WORKER_KEY))

 private var originalSender: Option[ActorRef] = None
 private var plans: List[String] = List.empty

 def receive: Receive = {
   case requestBuilding: RequestBuilding => handleRequestBuilding(requestBuilding)
   case responsePlans: ResponsePlans => handleResponsePlans(responsePlans)
   case responseMaterials: ResponseMaterials => handleResponseMaterials(responseMaterials)
   case responseBuilding: ResponseBuilding => handleResponseBuilding(responseBuilding)
 }

 private def handleRequestBuilding(requestBuilding: RequestBuilding) = {
   log.info(s"${getClass.getName()} Orchestrating building ...")
   originalSender = Some(sender)
   designerActor ! requestBuilding
 }

 private def handleResponsePlans(responsePlans: ResponsePlans) = {
   log.info(s"${getClass.getName()} Receiving plans...")
   plans = responsePlans.plans
   minerActor ! RequestMaterials(plans)
 }

 private def handleResponseMaterials(responseMaterials: ResponseMaterials) = {
   log.info(s"${getClass.getName()} Receiving materials...")
   workerActor ! RequestWorkerBuilding(plans, responseMaterials.materials)
 }
 private def handleResponseBuilding(responseBuilding: ResponseBuilding) = {
   log.info(s"${getClass.getName()} Receiving building...")
   originalSender.get ! responseBuilding
 }
}

The plans you refer to will soon be back in our hands

The next actor we need to implement is the Designer Actor. This one will be requested by Director Actor for delivering the plans with the design of the Death Star. After finishing his mission he must send a message to the Director Actor again with the content of the plans.

package com.datio.akka.demo.actor
import akka.actor.{Actor, ActorLogging, Props}
import com.datio.akka.demo.{RequestBuilding, ResponsePlans}

object DesignerActor {
 def props(): Props = Props(classOf[DesignerActor])
}

/**
 *  This actor is in charge of design the plans for building the Death Star.
 */
class DesignerActor extends Actor with ActorLogging{

 def receive: Receive = {
   case requestBuilding: RequestBuilding => handleRequest()
 }

 private def handleRequest() = {
   log.info(s"${getClass.getName()} Designing plans ...")
   val response = ResponsePlans(List[String]("sectionA","sectionB","sectionC"))

   sender ! response
 }
}

The strongest stars have hearts of kyber crystal

Now we have implemented the Director Actor we must implement the Miner Actor. This one has to collect the necessary materials according to the plans created by the Designer Actor. It’s necessary to send the plans to the Miner Actor, because this one needs to know how many materials have to be extracted from the mine. Once this actor recollects all the materials will send a message to the Director Actor with all the materials.


package com.datio.akka.demo.actor
import akka.actor.{Actor, ActorLogging, Props}
import com.datio.akka.demo.{RequestMaterials, ResponseMaterials}

object MinerActor {
 def props(): Props = Props(classOf[MinerActor])
}

/**
 * This actor is in charge of provide materials for building the Death Star according to the plan
 **/
class MinerActor extends Actor with ActorLogging {

 def receive: Receive = {
   case requestMaterials: RequestMaterials => handleRequest(requestMaterials)
 }

 private def handleRequest(request: RequestMaterials) = {
   log.info(s"${getClass.getName()} Extracting materials ...")
   val materials: List[String] = request.plans.map(s => "package" + s.split("section")(1))
   val response = ResponseMaterials(materials)

   sender ! response
 }
}

I assure you, Lord Vader. My men are working as fast as they can

Once the director has ordered the plans and the materials to Designer Actor and Miner Actor and both have finished, is the turn of the Worker Actor. This Director Actor should order the Worker Actor to complete the building using those plans and materials. Finally, after building the Death Star, the response will be sent to the Director Actor.


package com.datio.akka.demo.actor

import akka.actor.{Actor, ActorLogging, Props}
import com.datio.akka.demo.{RequestWorkerBuilding, ResponseBuilding}

object WorkerActor {
 def props(): Props = Props(classOf[WorkerActor])
}

/**
 * This actor is in charge of building the Death Star assembling the materials according to the plan
 **/
class WorkerActor extends Actor with ActorLogging {

 def receive: Receive = {
   case requestWorkerBuilding: RequestWorkerBuilding => handleRequest(requestWorkerBuilding)
 }

 private def handleRequest(request: RequestWorkerBuilding) = {
   log.info(s"${getClass.getName()} Building Death Star ...")

   val message = ResponseBuilding("Construction ready for doing evil ", "FINISHED", request.plans.zip(request.materials))

   sender ! message
 }
}

Now witness the firepower of this fully armed and operational battle station!


Once we have implemented all the actors we must create an Akka application class which initialises the Actor systems and launch the first message to the Director Actor. For this purpose we use the ask operator (?).

When the Actor System has finished to process all the internal requests, ask operator returns a future with the response message.


package com.datio.akka.demo

import akka.actor.{ActorSystem, Props}
import akka.event.Logging
import akka.pattern.ask
import akka.stream.ActorMaterializer
import akka.util.Timeout
import com.datio.akka.demo.Constants._
import com.datio.akka.demo.actor.DirectorActor

import scala.concurrent.duration._

/**
 * Main application
 */
object StarWarsApp extends App {

 implicit val timeout = Timeout(5 seconds)
 implicit val system = ActorSystem(MASTER_KEY)
 implicit val materializer = ActorMaterializer()
 implicit val executionContext = system.dispatcher
 val log = Logging(system, getClass)

 log.info(s">>> ${getClass.getName()} Initialising construction")
 val directorActor = system.actorOf(Props[DirectorActor], DIRECTOR_KEY)
 //Actor's first call with message
 val future = directorActor ? RequestBuilding()

 future onSuccess {
   case response: ResponseBuilding =>
     log.info(s">>> ---------------------------------")
     log.info(s">>> ${response.message}")
     log.info(s">>> execution : ${response.execution}")
     log.info(s">>> status : ${response.status.toString}")
     log.info(s">>> ---------------------------------")
   case None => log.error("error")
 }

 system.terminate()
}

If we execute the program we can see the result.

If you want to execute this example you can download from GitHub https://github.com/alvNa/akka-starwars-demo and execute it or play with it as you wish.

Impossible to see, the future is

This Actor Model design only expects to be a practical example of how to design and implement an Actor Model built on the Scala language. This is just the beginning, there are other many aspects of Akka we could apply after this initial case. For instance we could simulate a delay in the actors which provide elements using Akka scheduler.

Also, we could use Akka-Http for creating REST services for providing things like the materials, the Death Star, etc. Furthermore, we could improve the transitions between actors using forward operator and also we could use the pipe construct for sending the result of asynchronous blocks to an actor. Another aspect we could improve is to add unit tests to the code using Akka TestKit.
In general, we could add any Akka module or functionality we want for kicking the tires on this technology.

May the force be with you

After the previous post, we could learn the theoretical foundations of Akka toolkit and we could understand how the Actor Model works. In this one, we have used a practical approach showing how to design an Actor Model and the way to implement an Actor System using the Scala language.

If you are planning to develop high concurrent scalable applications with Akka, this is a good starting point for coding your applications. It’s up to you to use this example to add new features in order to achieve more complex functionalities.

You are on the council, but we do not grant you the rank of Master…


References

Images

mm

Álvaro Navarro

Over the last years I've been working as a software engineer in several projects for public and private companies, mainly developing web applications and web layered based architectures to support their development. Currently I am immersing myself in the Big Data world and it's technology stack.

More Posts

4 thoughts on “Akka Episode II – The Empire Strikes Back”

Comments are closed.