ps0604
ps0604

Reputation: 1071

Akka: send back response to Actor

I have the following code that sends back a response to the sender Actor (the responding actor loads a list from a table using Slick):

class ManageUsersData extends Actor {

  def receive = {
    case _       => {
      sender ! loadUsers
    }
  }


  def loadUsers =  {

    var list = new ListBuffer[String]()
    val db = Database.forConfig("dbconfig")
    try {

      val users: TableQuery[Users] = TableQuery[Users]
      val future = db.run(users.result)


      future onComplete {
        case Success(u) => u.foreach {
          user => {
            list += user.firstName
          }
          list
        }
        case Failure(t) => println("An error has occured: " + t.getMessage)
      }

    } finally db.close

   list

  }
}

The problem here is that loadUsers returns before waiting for the Future to complete. How can this be approached?

Upvotes: 1

Views: 803

Answers (3)

pagoda_5b
pagoda_5b

Reputation: 7373

As I see it, the easiest approach would be to simply send the future back to the sender, instead of the async-filled list.

As in

  def loadUsers =  {

    val db = Database.forConfig("dbconfig")
    try {

      val users: TableQuery[Users] = TableQuery[Users]
      
      val future = db.run(users.result)

      future.map {     //the future
        _.map {        //the returning Seq
          _.firstName 
        }
      }

    } finally db.close

  }

Now the caller actor has the burden to handle the future or the failure.

This also has the drawback that, if the sender used the ask/? operation, the async result will be a Future wrapping a further Future.

You can overcome this issue by using the pipeTo method, which sends a future message to the caller without needing to bother about unwrapping it.

The downside of piping the result is that the sender should have a way to identify which reply belongs to which request. A possible solution is to send a request identifier that will be sent back with the answer, so the requesting actor can easily link the twos.

sidenote

Why would you map on the firstName attribute in the future result instead of using a projection within the slick query? I assume it's for the sake of keeping the example simple.

Upvotes: 2

Ashalynd
Ashalynd

Reputation: 12563

The conceptual solution is already mentioned by Jean Logeart. Regarding loadUsers, I think this is a shorter version?

def loadUsers =  {

    val db = Database.forConfig("dbconfig")
    try {

      val users: TableQuery[Users] = TableQuery[Users]
      db.run(users.result).map(_.firstName)

    } catch {
      case e: Exception => println("An error has occured: " + e.getMessage)
    } finally db.close

 }

Upvotes: 3

Jean Logeart
Jean Logeart

Reputation: 53809

You should use the pipe pattern:

import akka.pattern.pipe

// ...

def receive = {
  val originalSender = sender
  loadUsers pipeTo originalSender
}

Upvotes: 3

Related Questions