Akka’s ActorSelection makes it possible to look up actors by logical path in the hierarchy:

1
val selection: ActorSelection = context.actorSelection("../processor/storage")

This selection can then be used like ActorRef in order to send messages to it using the tell or ask patterns:

1
2
3
selection ! Storage.Store("important words")

val allWords: Future[Seq[String]] = selection ? Storage.RetrieveAll

ActorSelection is therefore quite useful and allows more flexibility when designing an actor system given that trees can be built dynamically and queried dynamically at runtime. That being said, there are a few things that you should be aware of when using this mechanism.

ActorSelection is unverified

When you hold an ActorRef in your hands, you have something concrete and substantial and chances are high that the actor it points to exists (unless it has been stopped in the meanwhile). With ActorSelection there is no such guarantee: make a typo in the path, and you will notice only when sending messages to the selection - if you happen to watch closely the dead letter logs. What’s more, if you overuse this feature and tend to write out (hardcode) actor paths all accross your application, this might become a source of headache as the application grows and changes in case you rename actors or move them around in your hierarchy.

ActorSelection is less performant than ActorRef

The way that ActorSelection works is that it will do the lookup at message delivery time, for each message sent. That might be alright if you are sending a few messages, but if you send many messages and have a deep hierarchy to traverse, you are paying unnecessary lookup costs every time.

Use Identify when appropriate

If your actor needs to dynamically resolve another actor and then send it plenty of messages, a solution is to use the built-in Identify message that, when sent to an ActorSelection, will give you back the ActorRef - or nothing at all, if the ActorSelection doesn’t match anything. The good news is that it always gives you back an answer (of type ActorIdentity) so you know what is going on. Combine this technique with context.become and you get a simple way to safely bootstrap actors that use this mechanism:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class ResolvingActor extends Actor with Stash with ActorLogging {
  val CorrelationId = 42
  var storage: Option[ActorRef] = None

  context.actorSelection("../processor/storage") ! Identify(CorrelationId)

  def receive = initializing()

  def initializing(): Receive = {
    case ActorIdentity(CorrelationId, Some(ref)) =>
      storage = Some(ref)
      unstashAll()
      context.become(ready())
    case ActorIdentity(CorrelationId, None) =>
      log.error("Storage not ready, unable to process requests")
      context.stop(self)
    case _ =>
      stash()
  }

  def ready(): Receive = {
    case StoreSomething(message) =>
      // do the usual processing
      storage.foreach(_ ! Storage.Store(message))
  }
}

In conclusion, overusing ActorSelection means one of two things:

  • using it in too many places, with hardcoded paths that are tricky to maintain
  • using it to send too many messages as it has a performance cost