Completing request outside of main controller in Akka-http - scala

I am pretty new to the Akka world and I have to migrate a project from Spray to akka-http.
In spray, a route was of type Route = RequestContext ⇒ Unit.
But in akka-http, it is of type Route = RequestContext ⇒ Future[RouteResult].
So in spray, we would often handle and complete our requests through a chain of Actors (outside of the main controller) using only fire-and-forget so we didn't have to "ask" and the performance was great. Now, we have to use "ask" every time we pass the request to another Actor (correct me if I'm wrong)
I've been searching a lot and I found a few options which I'm not sure if they fully satisfy me (the need to complete a request in another Actor without the need to return it back to the controller). So that's where you could help me :)
Option 1: Using onSuccess/onComplete
Does using this block my main controller from receiving more requests?
Option 2: Using Futures and using RouteResult.Complete
I've found the following example at How to complete a request in another actor when using akka-http:
import akka.actor.{ActorSystem, Actor, Props}
import akka.pattern.ask
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{RequestContext, RouteResult}
import scala.concurrent.Future
import akka.http.scaladsl.model.HttpResponse
import akka.util.Timeout
class RequestActor extends Actor {
//business logic - returns empty HttpResponse
def handleRequestMessage(requestContext : RequestContext) =
RouteResult.Complete(new HttpResponse())
override def receive = {
case reqContext : RequestContext =>
sender ! handleRequestMessage(reqContext)
}
}//end class RequestActor
object RouteActorTest extends App {
implicit val as = ActorSystem("RouteActorTest")
implicit val timeout = new Timeout(1000)
val actorRef = as actorOf Props[RequestActor]
def handleRequest(reqContext : RequestContext) : Future[RouteResult] =
ask(actorRef,reqContext).mapTo[RouteResult]
val route = path("") { get(handleRequest) }
//rest of application...
}//end object RouteActorTest
But this actually passes the response back every time to the previous Actor (the sender) until it reaches the main controller. Another thing about this option is that in the code, it says:
/**
* The result of handling a request.
*
* As a user you typically don't create RouteResult instances directly.
* Instead, use the methods on the [[RequestContext]] to achieve the desired effect.
*/
So I'm not sure if it's a recommended way of doing it.
I've tried using requestContext.complete() in another actor but it doesn't work (no error thrown, simply doesn't send response)
Does anybody know what the best way is to implement the previous architecture we had?
Thanks a lot!

the need to complete a request in another Actor without the need to
return it back to the controller
You don't need to complete the request in another actor. All you need to do is for the actor that handles the request to send back a result, and you can use complete to signal completion of the future:
case class MyRequest(data: Int)
case class MyResult(data: Int)
class RequestActor extends Actor {
override def receive: PartialFunction[Any, Unit] = {
case MyRequest(data) => sender ! MyResult(data + 1)
}
}
And route:
get {
path("yuval") {
import scala.concurrent.ExecutionContext.Implicits.global
implicit val timeout = Timeout(5 seconds)
complete {
val result = (completionActor ? MyRequest(1)).mapTo[MyResult]
result.map(r => HttpResponse(StatusCodes.OK, entity = s"Result was ${r.data}"))
}
}
}
If you want to handle to handcraft the HttpResponse yourself, you can always use context.complete:
get {
path("yuval") {
import scala.concurrent.ExecutionContext.Implicits.global
implicit val timeout = Timeout(5 seconds)
context => {
val result = (completionActor ? MyRequest(1)).mapTo[MyResult]
result.flatMap(r => context.complete(HttpResponse(StatusCodes.OK, entity = s"Result was ${r.data}")))
}
}
}

Related

Akka Actor - pipeTo - Is it inadvisable to use values from the received message in the piped reply?

I'm handling a Future in an Actor with the pipeTo pattern which seems to be working ok.
In the example I've mocked up below UserProxyActor asks UserActivityActor with a Get(userId) message.
I want to include the parameters of the Get message in the response so that the receiving actor has everything it needs to process the message. For example, insert the activities into a DB with the related userId.
Is the userId available in the map call or does it get "closed over"?
Is this going to work because the ask pattern will block?
Is there some much nicer way to do this that I haven't come across?
class UserActivityActor(repository: UserActivityRepository) extends Actor {
import akka.pattern.pipe
import UserActivityActor._
implicit val ec: ExecutionContext = context.dispatcher
def receive = {
case Get(userId) =>
// user's historical activities are retrieved
// via the separate repository
repository.queryHistoricalActivities(userId)
.map(a => UserActivityReceived(userId, a)) // wrap the completed future value in a message
.recover{case ex => RepoFailure(ex.getMessage)} // wrap failure in a local message type
.pipeTo(sender())
class UserProxyActor(userActivities: ActorRef) extends Actor {
import UserProxyActor._
import akka.pattern.{ ask, pipe }
implicit val ec: ExecutionContext = context.dispatcher
implicit val timeout = Timeout(5 seconds)
def receive = {
case GetUserActivities(user) =>
(userActivities ? UserActivityActor.Get(user))
.pipeTo(sender())
}
}
Is the userId available in the map call or does it get "closed over"?
Get should be immutable if yes userId will be available.
Is this going to work because the ask pattern will block?
The actor receives a Get message, creates a Future and then process another message. No blocking at all. Ask's Future is not completed until the Future is completed or a timeout occurs.
Is there some much nicer way to do this that I haven't come across?
Looks nice if repository.queryHistoricalActivities(userId) is not blocking call.
I don't think there's anything wrong with what you did.
The only thing I want to mention is that I personally prefer not to use ask when communicating between actors (and keep in mind, this is largely a personal preference), so I'd do something like this:
class UserActivityActor(repository: UserActivityRepository) extends Actor {
import akka.pattern.pipe
import UserActivityActor._
implicit val ec: ExecutionContext = context.dispatcher
def receive = {
case Get(userId) =>
// user's historical activities are retrieved
// via the separate repository
repository.queryHistoricalActivities(userId)
.map(a => UserActivityReceived(userId, a)) // wrap the completed future value in a message
.recover{case ex => RepoFailure(ex.getMessage)} // wrap failure in a local message type
.pipeTo(sender())
class UserProxyActor(userActivities: ActorRef) extends Actor {
import UserProxyActor._
def receive = {
case GetUserActivities(user, sender()) =>
userActivities.forward(UserActivityActor.Get(user))
}
}
This does remove the time out behavior though (in your original implementation, the requesting actor will wait at most 5 seconds, or receive a failure, in mine, it may wait indefinitely).

How to make tests on akka actors response?

I am trying to understand the akka-Testkit", and hope it is ok to ask about it.
I found some tutorials and blogs that either access a state- or a lastMsg- attribute on the underlyingActor on the TestActorRef. However, a TestActorRef from the the "akka-testkit_2.11" % "2.4.10" does not have these attributes. I looked at the example on the akka website, and maybe I am missing something, but they show testing of among other an echo actor, but not with any simple actor implementations.
So, could someone help me understand how to test a worker that will respond with the same number if n % 3 == 0 (which is the case in the example). I would prefer not to use a future and the ask pattern if possible, and would like to make a test on the response that the actor will give (from that actors perspective by accessing its state or something similar).
class ProjectEulerScalaTestAkka extends TestKit(ActorSystem("testing")) with WordSpecLike with MustMatchers {
"A simple actor" must {
val actorRef = TestActorRef[Worker]
"receive messages" in {
actorRef ! 3
actorRef.underlyingActor.state//must not equal("world")
}
}
}
related:
How do I test an Akka actor that sends a message to another actor?
For now I am using a synchronized testing approach;
import akka.actor.ActorSystem
import akka.testkit.{TestActorRef, TestKit}
import org.scalatest.Matchers
import org.scalatest.WordSpecLike
import akka.pattern.ask
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.util.Success
class ProjectEulerScalaTestAkka extends TestKit(ActorSystem("testing")) with WordSpecLike with Matchers {
implicit val time = akka.util.Timeout(100 seconds)
"A simple actor" must {
val actorRef = TestActorRef[Worker]
"receive messages" in {
val f = (actorRef ? 3).asInstanceOf[Future[Int]]
val reply = f.value.get
reply should equal (Success(3))
}
}
}
What I did was mock the interface of the Actor that I was sending the message to, capturing the message and sending back a success message to the testActor reference.
success object with the captured message payload
case class SuccessWith(capturedMessage:Any = null)
a mock actor that you send your message to which, in turn, returns some value to the test actor
case class MockActor(requester: ActorRef) extends Actor {
override def receive: Receive = {
case i: Int => {
requester ! i
}
}
}
set up the actor you're wanting to unit test
val actorRef = system.actorOf(Props(new YourActor(args)))
and then your test
"A simple actor" must {
"receive messages" in {
val f = actorRef ! 3
expectMsg(Success(3))
}
}
}

How to test controllers that has akka system injected for Play framework?

Below is my controller:
package controllers
import java.util.TimeZone
import akka.actor.{ActorNotFound, ActorSystem}
import akka.util.Timeout
import com.google.inject.Inject
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
import play.api.Logger
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.json.{JsValue, JsError, JsSuccess}
import play.api.mvc._
import scala.concurrent.Future
import scala.concurrent.duration._
class ScheduleController #Inject()(system: ActorSystem) extends Controller {
val scheduler = QuartzSchedulerExtension.get(system)
implicit val timeout = new Timeout(5.seconds)
def index = Action.async {
Future.successful(Ok("hi"))
}
def run = Action.async { request =>
// find actor and "start" it by sending message to it, return Ok() if found
// if actor is not found, return BadRequest()
// if error return InternalServerError()
}
}
I've been looking for tutorials but most are outdated since they deal with Play 2.3.
I solved a very similar problem with Play2 controllers and actors except in my case I'm not injecting an ActorSystem into my controller. Rather, I use Play's default actor system and create actors from my global settings object when my application starts. For a given actor I provide a selection method in its companion object. For example:
class MyActor extends Actor {
def receive = {
case Request => sender ! Response
}
}
object MyActor {
val path = // assuming fixed actor path
val sys = // reference to play actor system
def select: ActorSelection = sys.actorSelection(path)
// my actor messages
case object Request
case object Response
}
The way I have this actor setup I plan on asking it from inside my controller method using a Request and expect to process a Response object back from it. The controller is a non-actor sender in this scenario. By calling ask on the actor selection, I close over the future and map the result to a partial function that matches the expected response. Here's what my controller looks like:
class MyController extends Controller {
implicit val timeout = // set your timeout duration
def index = Action.async {
MyActor.select ? Request map {
case Response => Ok("success")
}
}
}
Testing this controller endpoint is VERY challenging because your actor might not be in a ready state to start handling messages. I spent about two days trying to figure out how to properly block on the my actor's lifecycle state using the actor selection I've exposed through its companion object. What I ended up doing inside my test class that worked for me was to create a method that takes in a function parameter and loops over my actor state until I can resolve its ActorRef. I'm a fan of ScalaTest so this is what my unit tests amounted to:
class MySpec extends FlatSpec with Matchers with BeforeAndAfterAll {
implicit val fakeApp: FakeApplication = new FakeApplication()
override def beforeAll() = Play.start(fakeApp)
override def afterAll() = Play.stop(fakeApp)
def awaitableActorTest(assertions: => Any): Unit = {
val timeout = 1.second
var isActorReady = false
while(!isActorReady) {
val futureRef = Await.ready(MyActor.select.resolveOne(timeout), timeout)
futureRef.value.get match {
case Success(_) =>
assertions
isActorReady = true
case Failure(_) =>
Thread.sleep(2000)
}
}
}
it should "process actor request and response via controller endpoint" in {
awaitableActorTest {
val result = route(routes.MyController.index).get
status(result) shouldBe OK
contentAsString(result) shouldBe "success"
}
}
}
What I get out of this awaitableActorTest pattern is a really clean way of reliably hitting my controller endpoint only when my actor is alive and available to process messages. If it's not, my ActorRef using its selection companion won't resolve in the future with success -- instead it completes with failure. When it fails I sleep a few seconds and repeat the loop. I break the loop when I its selection successfully resolved an ActorRef.
I'm unfamiliar with the QuartzSchedulerExtension to which you refer in your code snippet, but I hope my example is useful.

Calling an Actor in a Spray route and waiting for the Actor's response

After have used Play! Framework for a while, I'm taking a first look to Spray. I started from a sample I found on GitHub, now I want to modify it but it's not easy for me to get how it works.
How can I wait for a message from an Actor in the code below?
package api
import akka.actor.ActorRef
import scala.concurrent.ExecutionContext
import spray.routing.Directives
import core.ClassifierActor
class ClassifierService(classifier: ActorRef)(implicit executionContext: ExecutionContext)
extends Directives with DefaultJsonFormats {
import ClassifierActor._
implicit val classifyMessageFormat = jsonFormat4(ClassifyMessage)
val route =
path("classify") {
post {
handleWith {
// The ClassifierActor gets a ClassifyMessage and
// sends a ClassifiedMessage back to the sender.
// How can wait for the ClassifiedMessage here
// and send a HttpResponse back?
cm: ClassifyMessage => classifier ! cm
// ???
}
}
}
}
Spray is already based on akka.io
Thus, if you want just to complete your route with actor response, you could use ask pattern
import akka.pattern.ask
import scala.concurrent.duration._
implicit val timeout = Timeout(5 seconds) // needed for `?` below
val route =
path("classify") {
post {
onComplete(actorResponse(yourActor, yourMessage)) {
complete(_)
}
}
}
def actorResponse[T](actor: ActorRef, msg: ClassifyMessage): Future[T] =
(actor ? msg).mapTo[T]
If you want to forward request to your actor model and complete route somewhere in actor system, you need to forward RequestContext to actors. Maybe, this example could help you. Good luck!
Take a look at my example project. This service uses Futures to complete routes. Like Rup commented, it's bad practice to wait for a response. Return a future immediately and let it complete when it gets a result.
In your example classifier ! cm is using the actor "tell" pattern. It sends a message cm to the classifier actor and moves on. If you want it to expect a response back in a future use the "ask" pattern: classifier ? cm. In your cm actor's receive method you will return a future with sender ! responseMsg which will return in a future.

Correct way of creating an ActorSystem inside an Actor

I'm using a third-party library (rediscala) to access a Redis DB inside my own Actor. Following is an example of how I'm currently doing it. Is this correct ? Are there any potential problems with the following code because I'm creating an akkaSystem inside my actor. If SimpleRedisClientActor crashes then I need to restart SimpleRedisClientActor which will create another Actor system. Should I override preStart and postStop ?
import akka.actor.{Props, Actor, ActorLogging}
import redis.RedisClient
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
class SimpleRedisClientActor extends Actor with ActorLogging {
implicit val akkaSystem = akka.actor.ActorSystem()
val redis = RedisClient()
def receive: Receive = {
case PingRedis => {
val futurePong = redis.ping()
futurePong.onComplete{
case Success(s) => log.info("Redis replied back with a pong")
case Failure(f) => log.info("Something was wrong ...")
}
}
}
}
object RedisActorDemo extends App {
implicit val akkaSystem = akka.actor.ActorSystem()
val simpleActor = akkaSystem.actorOf(Props(classOf[SimpleRedisClientActor]))
simpleActor ! PingRedis
}
object PingRedis
Not a good idea. ActorSystem provides runtime support for actors, so you need an ActorSystem to create an actor, not the other way around. Also, it takes about 500ms to start an ActorSystem, so you would not create lots of them - they are extremely heavyweight. In contrast, Actors are very lightweight. There should only be one ActorSystem per network node.