MojoJojo
MojoJojo

Reputation: 4232

Returning actual result from an Actor instead of Promise/Future

I'm trying to write an actor in Akka/Scala that calls an HTTP REST API and sends the result back to the calling actor. The API may returns a collection/vector of results that must first be converted to an internal vendor neutral format so that the vendor can be changed in future without much changes required on our code. Most of the code is working but I'm not sure how to unpack and send the inner vector.

This is the code I have and it returns a Promise to the invoking actor. What I would like to return is the actual vector that gets created in the final map operation:

class RESTActor extends Actor with ActorLogging with JsonSupport {

  final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system))
  val http = Http(context.system)

  import akka.pattern.pipe
  import context.dispatcher

  override def receive: Receive = {
    case query: String => {
      val requester = sender
      var uri = Uri(Settings.autoCompleteURL).withQuery(Query(Map("query" -> query)))

      sender! http
        .singleRequest(HttpRequest(HttpMethods.GET, uri = uri))
        .flatMap(response =>
          response.status match {
            case status if status.isSuccess() => Unmarshal(response.entity).to[VendorResponse].map(_.result.map(x => VendorNeutralResponse(x.id, x.field)))

            case _ => response.entity.toStrict(5.seconds).map { entity =>
              val body = entity.data.decodeString("UTF-8")
              log.warning(errorMessage(response, body))
              Left(errorMessage(response, body))
            }

          })
    }

  }

The calling actor:

pathPrefix("search") {
          get {
            parameter("query") { query =>
              {
                complete(restActor.ask(query)) //Doesn't work as the reply is a promise

              }
            }
          }
        }

How do I change the above code in the RESTActor to send actual result back instead of a future or promise?

Edit: After changing the code based on my own research and suggestions from @michał and @cyrille-corpet, the following code works:

pathPrefix("search") {
          get {
            parameter("query") { query =>
              {
                onComplete(gisRouter.ask(query)) {
                  case Success(resp: Future[Vector[VendorNeutralResponse]]) => {
                    resp.map(println)
                    complete("ok")
                  }
                  case Failure(e) => complete(e.toString)
                }

              }
            }
          }
        }

It seems like I'm still getting a future as a response from my Actor. How do I get the actor to respond with actual data and not a Future?

Upvotes: 2

Views: 361

Answers (2)

Michał
Michał

Reputation: 2484

You can do it monadic-like using map:

pathPrefix("search") {
  get {
    parameter("query") { query =>
      restActor.ask(query) map {
        case Success(resp) => ...
        case Failure(e) => ...
      }
    }
  }
}

EDIT: Currently your Actor responds with future. Try refactoring it so it does return unwrapped value instead:

class RESTActor extends Actor with ActorLogging with JsonSupport {

  final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system))
  val http = Http(context.system)

  import akka.pattern.pipe
  import context.dispatcher

  private def handleHttpResponse = {
    case status if status.isSuccess() => Unmarshal(response.entity).to[VendorResponse].map(_.result.map(x => VendorNeutralResponse(x.id, x.field)))

    case _ => response.entity.toStrict(5.seconds).map { entity =>
      val body = entity.data.decodeString("UTF-8")
      log.warning(errorMessage(response, body))
      Left(errorMessage(response, body))
    }

  }

  override def receive: Receive = {
    case query: String => {
      val requester = sender
      var uri = Uri(Settings.autoCompleteURL).withQuery(Query(Map("query" -> query)))

      http.singleRequest(HttpRequest(HttpMethods.GET, uri = uri)).flatMap(response =>
          response.status map handleHttpResponse ) pipeTo requester
    }

  }

Upvotes: 1

Cyrille Corpet
Cyrille Corpet

Reputation: 5315

You can use the onComplete function, that takes a Future[T] as input, and returns a Directive1[Try[T]], so you can use as following:

pathPrefix("search") {
  get {
    parameter("query") { query =>
      onComplete(restActor.ask(query)) {
        case Success(resp) => ...
        case Failure(e) => ...
      }
    }
  }
}

EDIT about the return from your actor, you should pipe the result of http.singleRequest to the sender, instead of telling it:

http.singleRequest(...).flatMap(...) pipeTo requester

That way, the actual tell (!) will be done only once the Future is resolved.

Upvotes: 1

Related Questions