October 6, 2014 · akka

Akka Notes - Actor Messaging - Request and Response - 3

Last time when we saw Actor messaging, we saw how fire-n-forget messages are sent (Meaning, we just send a message to the Actor but don't expect a response from the Actor).

Technically, we fire messages to Actors for its side-effects ALL THE TIME. It is by design. Other than not responding, the target Actor could ALSO do the following with that message -

  1. Send a response back to the sender (in our case, the TeacherActor would respond with a quote back to the StudentActor OR

  2. Forward a response back to some other Actor who might be the intended audience which in turn might respond/forward/have a side-effect. Routers and Supervisors are examples of those cases. (we'll look at them very soon)


Request & Response

In this write-up, we'll be focussing only on Point 1 - the request-response cycle.

Request-Response

The picture conveys what we are trying to achieve this time. For sake of brevity, I didn't represent the ActorSystem, Dispatcher or Mailboxes in the picture.

  1. The DriverApp sends an InitSignal message to the StudentActor.

  2. The StudentActor reacts to the InitSignal message and sends a QuoteRequest message to the TeacherActor.

  3. The TeacherActor, like we saw in the first discussion, responds with a QuoteResponse.

  4. The StudentActor just logs the QuoteResponse to the console/logger.

We'll also cook up a testcase to verify it.

Let's look at these 4 points in detail now :

1. The DriverApp sends an InitSignal message to the StudentActor

DriverApp

By now, you would have guessed what would the DriverApp do. Just 4 things :

  1. Initialize the ActorSystem
//Initialize the ActorSystem
  val system = ActorSystem("UniversityMessageSystem")
  1. Create the TeacherActor
//create the teacher actor
  val teacherRef = system.actorOf(Props[TeacherActor], "teacherActor")
  1. Create the StudentActor
//create the Student Actor - pass the teacher actorref as a constructor parameter to StudentActor
  val studentRef = system.actorOf(Props(new StudentActor(teacherRef)), "studentActor")

You'll notice that I am passing in the ActorRef of the TeacherActor to the constructor of the StudentActor so that the StudentActor could use the ActorRef for sending messages to the TeacherActor. There are other ways to achieve this (like passing in the Props) but this method would come in handy when we look at Supervisors and Routers in the following write-ups. We'll also be looking at child actors pretty soon but that wouldn't semantically be the right approach here - Student creating Teacher doesn't sound nice. Does it?

Lastly,

  1. The DriverApp would then send an InitSignal to the StudentActor, so that the StudentActor could start sending the QuoteRequest message to the TeacherActor.
//send a message to the Student Actor
  studentRef ! InitSignal

That's pretty much the DriverClass. The Thread.sleep and the ActorSystem.shutdown are just to wait for a couple of seconds for the message sending to finish before we finally shut down the ActorSystem.

DriverApp.scala

package me.rerun.akkanotes.messaging.requestresponse

import akka.actor.ActorSystem
import akka.actor.Props
import me.rerun.akkanotes.messaging.protocols.StudentProtocol._
import akka.actor.ActorRef

object DriverApp extends App {

  //Initialize the ActorSystem
  val system = ActorSystem("UniversityMessageSystem")

  //construct the teacher actor
  val teacherRef = system.actorOf(Props[TeacherActor], "teacherActor")
  
  //construct the Student Actor - pass the teacher actorref as a constructor parameter to StudentActor
  val studentRef = system.actorOf(Props(new StudentActor(teacherRef)), "studentActor")

  //send a message to the Student Actor
  studentRef ! InitSignal

  //Let's wait for a couple of seconds before we shut down the system
  Thread.sleep(2000)

  //Shut down the ActorSystem. 
  system.shutdown()

}

2. The StudentActor reacts to the InitSignal message and sends a QuoteRequest message to the TeacherActor

AND

4. The StudentActor receives the QuoteResponse from TeacherActor and just logs to the console/logger

Why did I combine Points 2 and 4? Because it is so simple you'll hate me if I separate them.

Student Teacher Request Response

So, Point 2 - the StudentActor receives the InitSignal message from the DriverApp and sends QuoteRequest to the TeacherActor.

def receive = {
  case InitSignal=> {
        teacherActorRef!QuoteRequest
  }
    ...
    ...

That's it !!!

Point 4 - The StudentActor logs the message that it receives from the TeacherActor.

Students logs message

Just, as promised :

case QuoteResponse(quoteString) => {
      log.info ("Received QuoteResponse from Teacher")
      log.info(s"Printing from Student Actor $quoteString")
}

I am sure you'd agree that it almost looks like pseudocode now.

So, the entire StudentActor class looks like :

StudentActor.scala
package me.rerun.akkanotes.messaging.requestresponse

import akka.actor.Actor
import akka.actor.ActorLogging
import me.rerun.akkanotes.messaging.protocols.TeacherProtocol._
import me.rerun.akkanotes.messaging.protocols.StudentProtocol._
import akka.actor.Props
import akka.actor.ActorRef

class StudentActor (teacherActorRef:ActorRef) extends Actor with ActorLogging {

  def receive = {
    case InitSignal=> {
      teacherActorRef!QuoteRequest
    }
    
    case QuoteResponse(quoteString) => {
      log.info ("Received QuoteResponse from Teacher")
      log.info(s"Printing from Student Actor $quoteString")
    }
  }
}

3. The TeacherActor responds with a QuoteResponse.

This is the exact same code as we saw in the fire-n-forget write-up.

The TeacherActor receives a QuoteRequest message and sends QuoteResponse back.

TeacherActor.scala

package me.rerun.akkanotes.messaging.requestresponse

import scala.util.Random

import akka.actor.Actor
import akka.actor.ActorLogging
import akka.actor.actorRef2Scala
import me.rerun.akkanotes.messaging.protocols.TeacherProtocol._


class TeacherActor extends Actor with ActorLogging {

  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)))

      //respond back to the Student who is the original sender of QuoteRequest
      sender ! quoteResponse

    }
  }
}

Testcases

Now, our testcase would simulate the DriverApp. Since, the StudentActor just logs the message and we won't be able to assert on the QuoteResponse itself, we'll just assert the presence of the log message in the EventStream (just like we talked last time)

So, our testcase looks like :


"A student" must {

    "log a QuoteResponse eventually when an InitSignal is sent to it" in {

      import me.rerun.akkanotes.messaging.protocols.StudentProtocol._
      
      val teacherRef = system.actorOf(Props[TeacherActor], "teacherActor")
      val studentRef = system.actorOf(Props(new StudentActor(teacherRef)), "studentActor")
      
      EventFilter.info (start="Printing from Student Actor", occurrences=1).intercept{
        studentRef!InitSignal
      }
    }
  }
  

Code

The entire project could be downloaded from github here.

Next up, we'll see how to use schedulers in Akka and monitoring your Akka app using Kamon