Handle asynchronous results of external dependencies in Akka actors

While writing the example source code for a former post using the cake pattern to manage external dependencies in Akka actors I ended up with the following test failure:

The implementation of fetching data from the backend repository was encapsulated into an asynchronous call to demonstrate the integration of a reactive layer as an external dependency like reactive-mongo or reactive-streams.

The dummy repository had the following trait declaration:

trait TaskRepository {
  def deleteByUser(userId: String): Future[Boolean]
  def insert(userId: String, task: Task): Future[Task]
  def getTasksByUser(userId: String): Future[Traversable[Task]]
}
trait TaskRepository {
  def getTasksByUser(userId: String): Future[Traversable[Task]]
}

This TaskRepository was used in the TaskActor as follows:

  def receive: Receive = {
    case GetTasks =>
      taskRepository.getTasksByUser(userId) map {tasks => sender ! tasks}
  }

The test case did look like this:

"TaskActor getByUser" should {
    "return tasks from repository" in new ActorTestScope {
      val userId = "noob"
      val probe = TestProbe()
      val taskActorComponent = new TaskActorComponentMock
      val actorRef = system.actorOf(TaskActorMock.props(userId, taskActorComponent))
      val tasks = Seq(Task("task1"), Task("task2"))

      taskActorComponent.taskRepository.getTasksByUser(isEq(userId)) returns Future.successful(tasks)

      probe.send(actorRef, GetTasks)
      probe.expectMsg(tasks)
    }
  }

The testcase never succeeded. Chasing the bug I found a very easy explanation:

After the taskRepository.getTasksByUser did return, the actor itself already finished the message processing. The sender reference which was used in the map operation of the future didn't point back to the correct TestProbe actor anymore. Instead it pointed to the DeadLetter queue.

A quick workaround fixed the problem:

def receive: Receive = {
    case GetTasks =>
      val origSender = sender
      taskRepository.getTasksByUser(userId) map {tasks => origSender ! tasks}
}

But does that look smooth enough?

Not really. So I ended up writing an extension function to the ActorRef class:

object TaskActor {
 implicit class ExtendedActorRef(self: ActorRef) {
    def ->(f: => Future[_]) = {
      f.map(self ! _)
    }
  }
}

Using this implicit enriched class I was able to rewrite the code as follows:

def receive: Receive = {
    case GetTasks =>
      sender -> taskRepository.getTasksByUser(userId)
}

Which looks to me much nicer and solved the async problem storing the reference to the sender in the enriched implicit class itself.

The code is available on GitHub.