Skip to content

Instantly share code, notes, and snippets.

@gautam1168
Last active September 20, 2016 11:49
Show Gist options
  • Save gautam1168/b7046e30104e3a024e47d539300af09a to your computer and use it in GitHub Desktop.
Save gautam1168/b7046e30104e3a024e47d539300af09a to your computer and use it in GitHub Desktop.
Akka with Scala

#What are actors Actors are classes that process messages. Akka provides Actor, that can be subclassed to create actors. All Actor subclasses must define a partial function named receive (defined using the case construct).

Actor objects are containers for State, Behavior, MailBox, Child Actors and Supervisor Strategy. All actors must minimize unnecessary interaction via locks or shared mutable states.

##Actor reference An actor object is always to be referenced using an Actor Reference object. These references can be passed around with impunity.

##State An actor has a state that can be in the form of datastructures, variables or pending messages. The state of an actor is valuable and must be protected from corruption by other actors. The state in Akka Actor is automatically protected as each one of these runs on a separate lightweitght-thread(?) that are shielded from everything else.

##Behavior The behavior is defined by the receive method of the Akka Actor class. This method is a partial function that accepts messages. The behavior of an actor may change during its lifecycle. The actor resets to the initial behavior every time it is restarted.

##MailBox Every actor has one and only one mailbox. The actor keeps processing the next available message. The order in which messages arrive from different senders is random. The messages from the same sender always arrive in order of sending. The mailbox can be implemented as a FIFO stack or a priority queue among other things.

##Child Actors An actor can create its own actors to delegate tasks. In Akka these are created and destoryed using context.actorOf() and context.stop(actorReference). These are asynchronous operations (hence non-blocking).

##Supervisor Strategy The supervisor strategy of an actor provides the rule for handling failure in its children. Each actor can have at most one strategy and this cannot be changed at runtime.

#Akka and Actor

##First user defined Actor Akka provides an Actor class that can be extended to provide a definition of receive method to handle messages. A simple Akka actor will just be a class but the documentation says that a companion object should also be created to provide a factory method for instantiation. This is how we define an Akka actor.

import akka.actor.{Actor, Props, ActorSystem}

class BasicActor extends Actor{
	def receive = {
		case "apple" => println("Received apple. I like apples!")
		case _ => println("generic message handling")
	}
}

We don't instantiate the actors ourselves. We have to use akka's Props and actorOf to do this. I think this is so because, an Akka actor should be part of a hierarchy of actors. At the root of the tree of actors is the root actor that is created for us by Akka. The root actor has two children, namely the user and system actors. Everything in the system actor's subtree is handled by Akka. All the actors we create are to be attached to the user actor's subtree. The only way to introduce actors in this tree is using actorOf which takes Props as an argument.

Here is how we can instantiate the actor we created.

object BasicActorMain extends App{
	//Create akka actor tree
	val actortree = ActorSystem("actortree")

	//Instantiate our actor in the tree
	val prop = Props[BasicActor]
	val basicActorInstance = actortree.actorOf(prop)
	
	//Send some messages to our actor
	basicActorInstance ! "apple"
	basicActorInstance ! "other message"
}

##Child actors Actors are supposed to form a tree. This is a consequence of the divide-and-conquerish approach actors use to solve any problem. Every actor keeps instantiating child actors and assigning work to them untill every actor has to do just one thing. This is how a child actor is created.

In the following program there are three actors. The FirstActor uses the SumOperator and ProductOperator actors to do some calculations. The instructions to do the calculations and the results are communicated to the actors using messages.

import akka.actor.{Props, Actor, ActorSystem}

//Messages that are sent around
case class Range(a: Int, b: Int)
case class SumOp(a: Int, b: Int)
case class ProdOp(a: Int, b: Int)
case class SumRes(ans: Int)
case class ProdRes(ans: Int)

//First child actor whose job it is to sum numbers in a range
class SumOperator extends Actor{
	def receive = {
		case Range(a, b) => {println("Summer received a Range")
							//Reply to sender of message with result
							sender() ! SumRes((a to b).foldLeft(0)(_+_))		
							}
		case _ => { println("Summer received weird message")
					sender() ! SumRes(42)
					}
	}		
}

//Second child actor who multiplies numbers in a range
class ProdOperator extends Actor{
	def receive = {
		case Range(a, b) => {println("Multiplier received a Range")
							sender() ! ProdRes((a to b).foldLeft(1)(_*_))
							}
		case _ => { println("Multiplier received weird message")
					sender() ! ProdRes(42)
					}
	}
}

//The controlling actor whom we send our messages
class FirstActor extends Actor{
	val summer = context.actorOf(Props[SumOperator])
	val multiplier = context.actorOf(Props[ProdOperator])
	def receive = {
		case SumOp(a, b) => {println("Sending message to summer")
							 summer ! Range(a, b)
							 }
		case ProdOp(a, b) => {println("Sending message to multiplier")
							  multiplier ! Range(a, b)
							 }
		case SumRes(ans) => println("Sum result: " + ans)
		case ProdRes(ans) => println("Product result: " + ans) 
	}
}

//Client program
object ActorWithChildMain extends App{
	val actortree = ActorSystem("root")
	val prop = Props[FirstActor]

	val act1 = actortree.actorOf(prop)
	act1 ! SumOp(1, 10)
	act1 ! ProdOp(1, 5)
}

#Dependency Injection This is a design pattern that specifies how to wire together the component classes in an application. Suppose there are classes A1, A2 and B2. Implementation of A1 needs instances of A2 and B2, i.e. A2 and B2 are dependencies of A1.

There are a bunch of ways this kind of code can be written:

  • A1 creates its dependencies. In this case the client code has no idea what A1 depends on. (If in a test you want to change arguments of A2, you will have to change the code of A1.)
  class A1(val a2_instance : A1, val b2_instance : A2){
    def this = {
      this.a2_instance = new A2("arg1", "arg2")
      this.b2_instance = new B2
    }
  }
  //Client code just instantiates A1
  object main extends App{
    A1 a1_instance = new A1
  }
  • A1 uses factories. Factories are classes in the global scope that instantiate the dependency classes.
  object A2Factory{
    val a2_instance = new A2("default1", "default2")
    def setA2(arg1 : String, arg2 : String) = {
      this.a2_instance = new A2(arg1, arg2)
    }
    def getA2() = {a2_instance}
  }
  class A1{
    var a2_instance = getA2()
    var b2_instance = new B2
  }
  
  object main extends App{
    //Now a test function can setup the factory differently if it wants
    A2Factory.setA2("arg1", "arg2")
    val a1_instance = new A1
  }
  • In the dependency injection pattern the dependencies are passed as arguments. This approach has the following advantages:
    • Compile time errors when dependencies are broken
    • No need for a global state that is maintained by the factories.
  class A1(val a2_instance : A2, val a1_instance : A1)
  //Client code just instantiates A1
  object main extends App{
    val a2_instance = new A2
    val b2_instance = new B2
    val a1_instance : A1 = new A1(a2_instance, b2_instance)
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment