daydreamer
daydreamer

Reputation: 92149

Ask pattern in actor hierarchy requires similar things at each level?

My Actor hierarchy for Monitoring looks like

    Monitor
    /     \
  Disk    Memory
         /      \
      Remote   Local

Where Monitor asks its children actors to get the data and so on. In the end Monitor receive all the data and prints it on console (for example).

Considering only Memory Hierarchy, I realized that in ask pattern, the call in hierarchy looked like

(at Memory level)

import ExecutionContext.Implicits.global
  implicit val timeout = Timeout(5 seconds)
  lpMemory <- (lpMemory ? CollectLPMemoryStat).mapTo[MemoryInformation] 

(at Remote Level)

  implicit val timeout = Timeout(5 seconds)
  import context.dispatcher
  (remoteActor ? HealthReportRequest).mapTo[MemoryInformation].pipeTo(sender)

So at every level in ask pattern, I needed to do 4 things

  1. Import ExecutionContext
  2. Specify the Timeout
  3. Transform the output mapTo[MemeoryInformation]
  4. send back to sender pipeTo(sender)

This gets cumbersome if there are more hierarchies.

Since I am new to Akka and Scala stack, I am looking to ideas where I can make this code much better

Thanks

Upvotes: 1

Views: 113

Answers (1)

Aleksandar Stojadinovic
Aleksandar Stojadinovic

Reputation: 5049

Here is what I would do, to cut the overhead a bit:

  1. Extract the timeout in a separate trait and mix it in to the actors. All in all, the actors are classes as any other, the OOP principles still stick to it. I would define the import ExecutionContext and specify the timeout into it.

    import akka.util.Timeout
    import scala.concurrent.duration._
    
    trait AskEnabled {
        implicit val timeout = Timeout(5 seconds)
        implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
    }
    

The implicit ec value is a bit of a hack, since I learned during the writing of this answer that the imports from the traits are actually not propagated. So, this may be slightly disputable, in case one day the framework does not expect ec but execContext for example, but in general keeps the code clean.

Anyway, the actor looks like:

class Actor1 extends Actor with AskEnabled {

val actor2 = context.actorOf(Props[Actor2])

override def receive: Receive = {
 case "send" => {
   val responseFuture = (actor2 ? "request").mapTo[String]
   responseFuture.onSuccess {
     case response: String => println(response)
   }
  }
 }
}
  1. I believe there is no need to both map and then pipe at the Remote Level, piping it directly should work well. The return type of the ? is a Future[Any], however it is only upcasted to Any, you don't actually lose the type information when shuffling it around. It's more from Scala's type inference thing rather than Akka. However the upcast is something Akka does, where the whole type safety thing gets blurry, at least until TypedActors become more popular, I suppose.

    (remoteActor ? HealthReportRequest).pipeTo(sender)

  2. If you have more a greater hierarchy in which you are reusing some functionality, then use the functional nature of Scala, extract that method (somewhere) and pass it to each actor. Functions are a first class citizen and you can pass them in the constructor, avoiding repeating yourself. This is a rough idea, you can also try mixing it in into a trait. Take a look at: Scala Passing Function with Argument .

Upvotes: 2

Related Questions