Now lets discuss the messaging part of Actors:
Broadly these are explained in the following six steps when a message is passed to the actor:
- Employee creates something called an
ActorSystem
- It uses the ActorSystem to create something called as
ActorRef
. The message(MSG) is sent to the ActorRef (a proxy to HR Actor)
- Actor ref passes the message along to a
Message Dispatcher
- The Dispatcher enqueues the message in the target Actor’s
MailBox
.
- The Dispatcher then puts the
Mailbox
on a Thread (more on that in the next section).
- The
MailBox
dequeues a message and eventually delegates that to the actual HR Actor’s receive method.
Let’s look at each step in detail now. You can come back and revisit these five steps once we are done.
The EmployeeActor
Program.
Lets consider this Employee Actor as the main program and lets cal it .
EmployeeActorApp
.
As we understand from the picture, the Employee Actor,
- Creates an
ActorSystem
- Uses the
ActorSystem
to create a proxy to the HRActor (ActorRef)
- Sends the
Message
to the proxy.
Let’s focus on these three points alone now.
Creating an Actor System
ActorSystem
is the entry point into the
ActorWorld
.
ActorSystems
are through which you could create and stop Actors. Or even shutdown the entire Actor environment.
On the other end of the spectrum, Actors are hierarchical and the ActorSystem is also similar to the
java.lang.Object
or
scala.Any
for all
Actors
- meaning, it is the root for all Actors. When you create an Actor using the ActorSystem’s
actorOf
method, you create an Actor just below the
ActorSystem
.
The code for initializing the ActorSystem looks like.
val system=ActorSystem("HrMessageingSystem")
The
HrMessageingSystem
is simply a name you give to your
ActorSystem
.
Creating a Proxy for HR Actor
val hrActorRef:ActorRef = actorSystem.actorOf(Props[HRActor])
The
actorOf
is the Actor creation method in
ActorSystem
. But, as you can see, it doesn’t return a HrActor which we need. It returns something of type
ActorRef
.
The
ActorRef
acts as a Proxy for the actual Actors. The clients do not talk directly with the Actor. This is Actor Model’s way of avoiding direct access to any custom/private methods or variables in the HrActor or any
Actor
for that sake.
To repeat, you send messages only to the
ActorRef
and it eventually reaches your actual Actor. You can
never talk to your
Actor
directly.
Send a Message to the Proxy
Now that we have an actor system and a reference to the actor, we would like to send requests to the HR actor reference. We send the message to an actor using the ! also called Tell.
hrActorRef ! Message.
The Tell is also called as fire-forget. There is no acknowledgement returned from a Tell.
When the message is sent to an actor, the actor’s receive method will receive the message and processes it further. receive is a partial function and has the following signature:
def receive: PartialFunction[Any, Unit]
The return type receive suggests it is Unit and therefore this function is side effecting.
Here is the sample program for what we have discussed till now:
object EmployeeActorApp extends App{
val actorSystem=ActorSystem("HrMessageingSystem")
val hrActorRef=actorSystem.actorOf(Props[HrActor])
hrActorRef!Message
Thread.sleep (2000)
actorSystem.shutdown()
}
You’ll have to shutdown the ActorSystem or otherwise, the JVM keeps running forever. And I am making the main thread sleep for a little while just to give the HrActor to finish off its task.
The Message
We just told a Message to the
ActorRef
but we didn’t see the message class at all!
object Message{
case class Message()
case class Message(someString:String)
}
As we know, the
Message
is for the requests that come to the
HrActor
. The Actor would respond back with a
MessageResponse
.
Dispatcher & A Mailbox
The
ActorRef
delegates the message handling functionality to the Dispatcher. Under the hood, while we created the
ActorSystem
and the
ActorRef
, a
Dispatcher and a
MailBox was created. Let’s see what they are about.
MailBox
Ever Actor has one MailBox . As Per our analogy, every HR has one mailbox too. The HR has to check the mailbox and process the message. In Actor world, it’s the other way round - the mailbox, when it gets a chance uses the Actor to accomplish its work.
Also the mailbox has a queue to store and process the messages in a FIFO fashion - a little different from our regular inbox where the most latest is the one at the top.
Dispatcher
Dispatcher does some really cool stuff. From the looks of it, the Dispatcher just gets the message from the
ActorRef
and passes it on to the
MailBox. But there’s one amazing thing happening behind the scenes :
The Dispatcher wraps an
ExecutorService (ForkJoinPool or ThreadPoolExecutor). It executes the
MailBox against this
ExecutorService.
Check out this code snippet from the Dispatcher:
protected[akka] override def registerForExecution(mbox: Mailbox, ...): Boolean = {
...
try {
executorService execute mbox
...
}
HR Actor
The
MailBox, when it gets its run method fired, dequeues a message from the message queue and passes it to the Actor for processing.
The method that eventually gets called when you tell a message to an
ActorRef
is the receive method of the target
Actor
.
The Hr
Actor
is a rudimentary class which has a List of quotes and obviously the receive method which handles the messages.
HRActor.scala :
class HRActor extends Actor {
def receive = {
case s: String if(s.equalsIgnoreCase(“SICK”)=> println("Sick Leave Applied”)
case s: String if(s.equalsIgnoreCase(“PTO”) => println("PTO applied “)
}
}
The HRActor’s receive method pattern matches for the Messages passed - the Message (actually, it is a good practice to pattern match the default case but there’s an interesting story to tell there)
All that the receive method does is to,
- Pattern match for Message
- Check the message
- Check the type of the Message
- Process the message based on the type of message.