In this first part of Actor Messaging, we'll create the Teacher Actor and instead of the Student Actor, we'll use a main program called StudentSimulatorApp
.
Revisiting Student-Teacher in Detail
Let's for now consider the message sent by the StudentSimulatorApp
to the TeacherActor alone. When I say StudentSimulatorApp
, I just mean a normal main program.
The picture conveys this :
(if the terms are overwhelming, don't worry, we'll go through them in detail)
- Student creates something called an
ActorSystem
- It uses the
ActorSystem
to create something called asActorRef
. TheQuoteRequest
message is sent to theActorRef
(a proxy toTeacherActor
) - Actor ref passes the message along to a
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 Teacher Actor's receive method.
Like I said, don't worry about it. Let's look at each step in detail now. You can come back and revisit these five steps once we are done.
The StudentSimulatorApp
program
We would use this StudentSimulatorApp
to bring up the JVM and initialize the ActorSystem
.
As we understand from the picture, the StudentSimulatorApp
- Creates an
ActorSystem
- Uses the
ActorSystem
to create a proxy to the Teacher Actor (ActorRef
) - Sends the
QuoteRequest
message to the proxy.
Let's focus on these three points alone now.
1. Creating an ActorSystem
ActorSystem
is the entry point into the ActorWorld
. ActorSystem
s 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("UniversityMessageSystem")
The UniversityMessageSystem
is simply a cute name you give to your ActorSystem
.
2. Creating a Proxy for TeacherActor?
Let's consider the following snippet :
val teacherActorRef:ActorRef=actorSystem.actorOf(Props[TeacherActor])
The actorOf
is the Actor creation method in ActorSystem
. But, as you can see, it doesn't return a TeacherActor
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 TeacherActor
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. People will hate you to death if you find some mean ways to do that.
3. Send aQuoteRequest to the Proxy
It's an one liner again. You just tell the QuoteRequest
message to the ActorRef
. The tell
method in Actor
is actually !
. (there's also a tell method in ActorRef which just delegates the call back to !
)
//send a message to the Teacher Actor
teacherActorRef!QuoteRequest
That's it !!!
If you think I am lying, check the entire code of the StudentSimulatorApp
below :
StudentSimulatorApp.scala
package me.rerun.akkanotes.messaging.actormsg1
import akka.actor.ActorSystem
import akka.actor.Props
import akka.actor.actorRef2Scala
import me.rerun.akkanotes.messaging.protocols.TeacherProtocol._
object StudentSimulatorApp extends App{
//Initialize the ActorSystem
val actorSystem=ActorSystem("UniversityMessageSystem")
//construct the Teacher Actor Ref
val teacherActorRef=actorSystem.actorOf(Props[TeacherActor])
//send a message to the Teacher Actor
teacherActorRef!QuoteRequest
//Let's wait for a couple of seconds before we shut down the system
Thread.sleep (2000)
//Shut down the ActorSystem.
actorSystem.shutdown()
}
Well, I cheated a little. 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 TeacherActor
to finish off it's task. I know this sounds stupid. Don't worry about it. We'll write some neat testcases in the next part in order to avoid this hack.
The Message
We just told a QuoteRequest
to the ActorRef
but we didn't see the message class at all !!
Here it comes :
(It is a recommended practice to wrap your messages in a nice object for easier organization)
TeacherProtocol
package me.rerun.akkanotes.messaging.protocols
object TeacherProtocol{
case class QuoteRequest()
case class QuoteResponse(quoteString:String)
}
As you know, the QuoteRequest
is for the requests that come to the TeacherActor
. The Actor would respond back with a QuoteResponse
.
Dispatcher and 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
(we'll see one special case later). Per our analogy, every Teacher has one mailbox too. The Teacher 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.
Now, the 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 snippet from the Dispatcher
protected[akka] override def registerForExecution(mbox: Mailbox, ...): Boolean = {
... try {
executorService execute mbox ...
}
What? Did you just say you execute the MailBox?
Yup. We already saw that the MailBox
holds all the messages in a Queue. Also since the Executor runs the MailBox, the MailBox must be a Thread. You're right. That's pretty much MailBox's declaration and constructor.
Here's the signature of the Mailbox
private[akka] abstract class Mailbox(val messageQueue: MessageQueue) extends SystemMessageQueue with Runnable
Teacher 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 TeacherActor is a rudimentary class which has a List of quotes and obviously the receive
method which handles the messages.
Check this out :
TeacherActor.scala
package me.rerun.akkanotes.messaging.actormsg1
import scala.util.Random
import akka.actor.Actor
import me.rerun.akkanotes.messaging.protocols.TeacherProtocol._
/*
* Your Teacher Actor class.
*
* The class could use refinement by way of
* using ActorLogging which uses the EventBus of the Actor framework
* instead of the plain old System out
*
*/
class TeacherActor extends Actor {
val quotes = List(
"Moderation is for cowards",
"Anything worth doing is worth overdoing",
"The trouble is you think you have time",
"You never gonna know if you never even try")
def receive = {
case QuoteRequest => {
import util.Random
//Get a random Quote from the list and construct a response
val quoteResponse=QuoteResponse(quotes(Random.nextInt(quotes.size)))
println (quoteResponse)
}
}
}
The TeacherActor
's receive
method pattern matches for just one Message - the QuoteRequest
(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 QuoteRequest
- pick a random quote from the static list of quotes
- construct a QuoteResponse
- print the QuoteResponse to the console