Typed ask for Akka

Akka is a great tool for writing distributed applications. One thing that always surprised me though is that while being based on Scala, which is a very type-safe language, the elementary construct in Akka – an actor – is not really type safe. You can send any message to any actor, and get back any object in reply.

The upcoming 2.2 release of Akka will contain an experimental implementation of typed channels, which use macros to ensure type-safety in actor communication; but before that is final, here’s a simpler and less powerful approach, to add some typing to the ask pattern.

You can look at the ask pattern as a kind of asynchronous method invocation: you send a message (its class corresponds to the method name) with some arguments, and expect a reply (method result). More specifically, we get back a Future, which will eventually hold the reply (if any). Note that the reply can also be of type Unit, corresponding to methods returning no result. Knowing when such “methods” complete may still be useful, though.

A very simple example of the basic ask pattern usage:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import akka.pattern.ask
 
case class LookupUser(id: Int)
 
// the actor impl should send back a message to the sender
val userFuture = actor ? LookupUser(10)
 
// userFuture: Future[Any]
 
userFuture onSuccess {
   case result => {
      // do something with the result
      // result has type Any 
   }
}

The not-so-nice thing here is that the return type of ? (see the AskSupport implementation) is Future[Any], as the actor may respond with any message. However ideally, when sending LookupUser we would want to get a Future[Option[User]], when sending UserCount a Future[Int] and so on.

This is in fact quite easy to implement. First of all, we must somehow embed the expected reply type in the message. For that we can use a trait, which takes the type of the expected response as a type parameter:

1
trait Replyable[T]

This can be used in the messages that we are sending to the actor:

1
2
case class LookupUser(id: Int) extends Replyable[Option[User]]
case class UserCount() extends Replyable[Int]

Now we need a variant of ? which returns a future with the right type parameter:

1
2
3
4
5
6
7
8
9
10
trait ReplySupport {
  implicit class ReplyActorRef(actorRef: ActorRef) {
    def ?[T](message: Replyable[T])
            (implicit timeout: Timeout, tag: ClassTag[T]): Future[T] = {
      akka.pattern.ask(actorRef, message).mapTo[T]
    }
  }
}
 
package object reply extends ReplySupport

You can see that we are simply re-using the existing ask implementation, and mapping the resulting future to the right type. The timeout is an implicit parameter of ask and ClassTag of mapTo, hence we must include them in the signature as well.

Usage is quite simple, in fact it’s almost the same as before, except for the import and that the future is of the right type:

1
2
3
4
5
6
7
8
9
10
11
12
import reply._
 
val userFuture = actor ? LookupUser(10)
 
// userFuture: Future[Option[User]]
 
userFuture onSuccess {
   case result => {
      // do something with the result
      // result has type Option[User] 
   }
}

That’s the actor-user side. What about the actor itself? How to ensure that if an actor receives a message of type Replyable[T], it will actually answer with T? As quite commonly in Scala, the answer again is a trait, which can be mixed into an actor:

1
2
3
4
5
6
7
8
9
10
11
12
13
trait ReplyingActor extends Actor {
  def receive = {
    case m: Replyable[_] if receiveReplyable.isDefinedAt(m) => {
      try {
        sender ! receiveReplyable(m)
      } catch {
        case e: Exception => sender ! Failure(e)
      }
    }
  }
 
  def receiveReplyable[T]: PartialFunction[Replyable[T], T]
}

And example usage:

1
2
3
4
5
6
class UserActor extends ReplyingActor {
   def receiveReplyable[T] = {
      case LookupUser(id) => Some(User(...))
      case UserCount() => 512
   }
}

Now this is all nicely type-checked. If we tried to return a String in the UserCount branch, we would get a compile-time error.

Hope this will be helpful – and comments as always are welcome!

Adam

  • http://twitter.com/fernandezpablo Barra de Carbono

    Aren’t typed-actors a better fit for this case?

    http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html

  • http://www.warski.org/ Adam Warski

    They could be, and they certainly deserve a mention in the subject of the blog post; although with typed actors you kind of break out of the actor model. You also need to remember to return completed promises (and not create a future enclosing on the actor’s state, for example). So in a sense it depends on how close you want to stay to the “core” actor model.

    See also this discussion on StackOverflow:
    http://stackoverflow.com/questions/12516910/akka-typedactor-vs-writing-my-own-static-interface-to-an-actor-class

    And this blog on LetItCrash:
    http://letitcrash.com/post/19074284309/when-to-use-typedactors

  • http://www.warski.org/ Adam Warski

    Also, when reading code that uses a typed actor, it is much less obvious that the actual method invocations are entirely asynchronous. Using an explicit actor with the familiar actor syntax (! and ?) may boost clarity of the code, and better express the intent.

  • ericacm

    That’s really interesting. I didn’t realize that each case of a partial function like
    def receiveReplyable[T]: PartialFunction[Replyable[T], T]
    could have a different T.

  • Pingback: ElasticMQ 0.7.0: long polling, non-blocking implementation using Akka and Spray | Blog of Adam Warski()

  • Egon Nijns

    This is a very helpful pattern – thanks for posting.
    When trying to use this in my own code I had a small problem. Some of my actors are receiving non-replyable messages that just update their state.

    I extended ReplyingActor a little bit to facilitate this:
    https://gist.github.com/enijns/5848692

  • Universal178

    Could you please post the whole code?

  • http://www.warski.org/ Adam Warski

    The code is a simplified and slightly modified version of what I use in ElasticMQ: https://github.com/adamw/elasticmq/tree/master/core/src/main/scala/org/elasticmq/actor/reply

  • davegurnell

    Great post – thanks very much! I have a suggestion for improvement having cribbed your approach in my code…

    In the implementation of receive in ReplyingActor, use “pipeTo” rather than “!” to return the message to its sender. This causes exceptions to be passed back as well as successful results. Otherwise you lose the exceptions and end up with timeouts:

    // Swap line 5 in the ReplyingActor snippet for this…

    receiveReplyable(m) pipeTo sender

    // You’ll need this import to use pipeTo…

    import akka.pattern.pipe

    Cheers!

    Dave

  • http://www.warski.org/ Adam Warski

    Thanks for the tip! Actually the code contains some exception-handling with a try-catch, but this is certainly nicer.

  • davegurnell

    Ack! My bad – I forgot I changed something when I implemented your code.

    I altered receiveReplyable to return a Future[T, making it easier to call out to further async operations. I obviously didn’t alter the body of receive to match.

    Hence the need for pipeTo – the try/catch obviously doesn’t catch exceptions thrown in the future returned by my version of receiveReplyable.

    So your example is fine and the bug is entirely my fault. Your receiveReplyable is synchronous so your try/catch will work perfectly :)