Getting Started With Actors: Akka in a Nutshell

Getting Started With Actors: Akka in a Nutshell
Getting Started With Actors: Akka in a Nutshell
Akka uses its hierarchical actor system to encapsulate state and behavior. See how actors are built from one another and how they communicate.

Actors are objects which encapsulate state and behavior. They communicate by exchanging messages. We can consider an actor as a person.

Like a software development team, actors form hierarchies. In a team, there is a project manager who takes requirements from client and distributes it over to different team leads. Then, those team leads further distribute the tasks to other team members. Team members, after finishing their tasks, report to their team lead about the status.

Similarly, an actor might want to split up a task into smaller tasks. For that, the actor creates child actors, and if by any means the child actor is unable to execute successfully, then it will send a corresponding failure message to its parent.

Actor System

An actor system manages the resources it is configured to use in order to run the actors it contains.

The actor system, as a collaborating ensemble of actors, is the natural unit for managing shared facilities, like scheduling services, configuration, logging, etc.

Components of the Actor System
Creating an Actor With Props

Props is a configuration class to specify options for the creation of actors. Here are some examples of how to create a Props instance:

val props1 = Props[MyActor] val props2 = Props(new ActorWithArgs(“args”)) val prop3 = Props(classOf[ActorWithArgs], “arg”)

Note : The recommended approach to create the Props actor is not supported for cases when the actor constructor takes value classes as arguments.

Declaring one actor within another is very dangerous and breaks actor encapsulation. Never pass an actor’s reference into Props!

The recommended practice is defining the Props method inside the companion object.

object MyActor { def props(num: Int): Props = Props(new MyActor(num)) } class MyActor(num: Int) extends Actor { def receive = { case x: Int => sender() ! (x + num) } } class MyAnotherActor extends Actor { context.actorOf(MyActor.props(10), "my-actor") }

Also, declaring what messages an actor can receive in its companion object is a recommended practice.

object MyActor { case class Greeting(from: String) case object Bye } class MyActor extends Actor { import MyActor._ def receive = { case Greeting(greeter) => println(s”Hello $greeter”) case Bye => println(s”Good by everyone”) } }

Tell vs. Ask

There are two ways we can talk to an actor:

object MyActor { case class Sum(a: Int, b: Int) } class MyActor extends Actor { override def receive: Receive = { case Sum(a: Int, b: Int) => sender() ! (a + b) case _ => sender() ! 0 } } object TellAndAsk extends App { implicit val duration: Timeout = 20 seconds val system = ActorSystem("ask-and-tell") val myActor = system.actorOf(Props[MyActor], "my-actor") val sum: Future[Int] = (myActor ? Sum(3,4)).asInstanceOf[Future[Int]] println("Sum of 3 and 4 is " + Await.result(sum, Duration.Inf)) system.terminate() }

ActorRef vs. ActorSelection vs. ActorPath

ActorRef, ActorSelection, and ActorPath are all important parts of the Akka actor system. Let's cover them and their uses one by one.

ActorRef

ActorRef is the interface for the actor system and a reference for a single actor. When an actor stops actorRef, it starts pointing to the Dead Letter Office. That means that if any message is sent through that actor reference, it will go to the Dead Letter Office instead. Each time we create a new actor, Akka will create a new actor reference. We can create an actor reference using the actorOf method.

val system = ActorSystem(“my-actor-system”) /*Here the actor is an actor reference*/ val actor = system.actorOf(Prop[MyActor])

ActorPath

Actors are created in a strictly hierarchical fashion. Every actor in an actor system is given a unique name. As mentioned earlier, every actor created has a parent — either a user guardian or another user-created actor. So, the path for any actor is like a file system. It starts with the root (/), then the user, then the actor. Let’s say we created an actor with the name my-actor . Its path for will be: /user/my-actor .

Similarly, if we create any actor from my-actor , then the path will look like this: /user/my-actor/ (note the root at the end — "/").

ActorSelection

ActorSelection is another way to represent an actor. Just like actorRef, we can send messages via ActorSelection. The difference is that it is created from the actor path.

system.actorSelection("/user/parent/child")

What also makes ActorSelection unique is that it stays valid even after an actor dies.

Check out the source code for ActorSelection demonstrating just that.

So that’s it for now! If you're interesting in reading more, check out our already covered topics

In our further blog series, we’ll cover the following topics: