How Pass Messages Between Actors in Akka Actor System?

Reading Time: 4 minutes

Introduction of Akka Actors

Akka is a great library for message passing between actors or applications and building scalable, resilient, and distributed applications. If you are working on an application based on Microservices-driven architecture and scalability is a big concern, then go for Akka Actor System.

Characteristics of Akka Actors

The best way to think of using Akka is the “Everything is an actor” approach.

The model shortly describes what is an akka actor system & what it can do:

  1. Receive messages from other actors and respond.
  2. Change behavior in respond to message (if this is the logic)
  3. Create new actors (child)
  4. Send messages to other actors

Akka Actor System

The API documentation describes an ActorSystem like this:
“An actor system is a hierarchical group of actors which share common configuration,
e.g. dispatchers, deployments, remote capabilities, and addresses. It is also the entry point
for creating or looking up actors.”
An ActorSystem is a structure that allocates one or more threads for your application,
so you typically create one ActorSystem per (logical) application.

// an actor needs an ActorSystem
val system = ActorSystem("HelloSystem")
// create and start the actor
val helloActor = system.actorOf(Props[HelloActor], name = "helloactor")
Sending a message to an actor is handled by the actor system via method calls to what is referred to as an actor reference.
Message Passing Between Actors

Sending a message to an actor is handled by the actor system via method calls to what is referred to as an actor reference. Actor references represent a transparent reference to an actor that may be located somewhere within an actor system, and the actor system may actually be distributed across multiple JVMs that reside on multiple network nodes.

How to Communicate Between Actors?

Actors should be sent immutable messages with the ! method.
When an actor receives a message from another actor, it also receives an implicit reference named sender, and it can use that reference to send a message back to the originating actor.
The general syntax to send a message to an actor is:

actorInstance ! message

For example, if you have an actor instance named car, you can send it a start message like this:

car ! "start"

In this case, the message is the String literal start. The car actor should receive this message in a match expression in its receive method, and from there it can send a message back to whoever sent the start message. A simplified version of a receive method for a car might look like this:

def receive = {
case "start" =>
val result = tryToStart()
sender ! result
case _ => // do nothing
}

Threads in Actor System

Objects typically process within a single thread of execution. Of course, using multiple threads is an option, but the most common case is that a single thread handles the flow of objects invoking methods of other objects. With actors, the sender of a message and the receiving actor are separated. The message sender does not directly interact with the message receiver. As a result, the message sender and the message receiver are running on separate threads. Also, the message sender and the message receiver may be running in separate processes, and those processes may be running on separate network nodes. This separation of the message sender and message receiver also applies to message passing between networked services, such as microservices.

Communication Between Actors

To demonstrate a more complicated example of actors communicating, the following code shows how to send messages back and forth between Akka actors.

import akka.actor._
case object PingMessage
case object PongMessage
case object StartMessage
case object StopMessage

class Ping(pong: ActorRef) extends Actor {
var count = 0
def incrementAndPrint { count += 1; println("ping") }
def receive = {
case StartMessage =>
incrementAndPrint
pong ! PingMessage
case PongMessage =>
incrementAndPrint
if (count > 99) {
sender ! StopMessage
println("ping stopped")
context.stop(self)
} else {
sender ! PingMessage
}
case _ => println("Ping got something unexpected.")
}
}
class Pong extends Actor {
def receive = {
case PingMessage =>
println(" pong")
sender ! PongMessage
case StopMessage =>
println("pong stopped")
context.stop(self)
case _ => println("Pong got something unexpected.")
}
}
object PingPongTest extends App {
val system = ActorSystem("PingPongSystem")
val pong = system.actorOf(Props[Pong], name = "pong")
val ping = system.actorOf(Props(new Ping(pong)), name = "ping")
// start the action
ping ! StartMessage
// commented-out so you can see all the output
//system.shutdown
}

Actors should communicate by sending immutable messages to each other. There are four messages, and they’re defined using case objects: PingMessage, PongMessage, StartMessage, and StopMessage.
The PingPongTest object performs the following work:

  1. Creates an ActorSystem .
  2. Creates pong , an instance of the Pong actor. (The pong object is actually an instance
    of ActorRef , though I loosely refer to it as an actor, or actor instance.) The Pong
    actor constructor does not require any arguments, so the noargs Props syntax is
    used.
  3. Creates ping , an instance of the Ping actor. The Ping actor constructor takes one
    argument, an ActorRef , so a slightly different version of the Props syntax is used.
  4. Starts the ping/pong action by sending a StartMessage to the ping actor.
    Once ping receives the StartMessage , the actors send messages back and forth between
    each other as fast as they can until the counter limit in ping is reached. Messages are
    sent using the usual ! method.
  5. To get things started, the Ping class needs an initial reference to the Pong actor, but once the action starts, the two actors just send a PingMessage and PongMessage to each other using the sender references they implicitly receive, until the Ping actor count limit is reached. At that time, it sends a StopMessage to the Pong actor, and then both actors call their context.stop methods. The context object is implicitly available to all actors and can be used to stop actors, among other uses.

References

https://doc.akka.io/docs/akka/current/typed/index.html?_ga=2.45825764.1737148851.1639312186-1364780834.1635785150

https://www.lightbend.com/blog/how-akka-works-at-most-once-message-delivery

Scala Future

Written by 

Gulshan Singh is a Software Intern at Knoldus Inc. Software Developers are forever students. He is passionate about Programming.

Leave a Reply