Spray with Akka Starter Kit


Over the last few months, Spray is fast becoming the de-facto for all the products that we are working on. Irrespective of whether it is a product which has a full featured UI or a gaming component which needs to provide services for the game to execute in a performant way, Spray has found its use.

The best part about Spray being that is very performant and endorses the asynchronous, non blocking style that we are used to with Akka. It satisfies the HTTP integration needs of any product. More information on this webinar.

The starter kit which is the main part of this post, is a template which allows you to accept incoming REST requests, route them to the corresponding Akka actor which provides a response and then carrying back this response to the actual user in a non-blocking, performant way. Note that the example is very simple but it attempts to give you a starting point on which you can build further.

The main characters of this example are
1. The HUB which is responsible for communication to and from all modules.
2. Modules which would interact with the HUB
3. Client call which would call a REST service on the HUB and await a response.

Let us see how the interactions happen. For simplicity, we would assume that the client is making a GET call on the browser with something like

http://localhost:8080/module/BANG

Now the call lands up on the HUB which has exposed a REST service. The HTTP server on Spray is started like this

object StartHub extends App with ActorHelper {

  implicit val system = hubActorSystem
  // create and start Email services
  val service = system.actorOf(Props[HubServices], "hubServices")
  // start a new HTTP server on port 8080 with our service actor as the handler
  IO(Http) ! Http.Bind(service, interface = "localhost", port = 8080)
}

Ofcouse you would be externalizing a few things like host and port. I am being lazy ūüėČ

If you notice, we are binding to the service called hubservices. This what wraps over our routes.

class HubServices extends Actor with HubRoutes {
  def actorRefFactory: ActorContext = context
  def receive: Receive = runRoute(hubRoutes)
}

and the hubRoutes are defined as

val hubRoutes = {
    path("module" / Segment) { (emailText) =>
      get {
        logger.info("Get called with request")
        respondWithMediaType(`text/html`) { ctx =>
          val future = hrService ? emailText
          processFutureResult(future, ctx)
        }
      }
    } ~
      path("module") {
        entity(as[String]) { emailData =>
          post {
            respondWithMediaType(`text/html`) { ctx =>
              ctx.complete(s"Currently I cannot process data with $emailData")
            }
          }
        }
      }

Now the fun part, we are accepting 2 kinds of requests as per the routes defined. One is a GET request of the kind /module/XXX when XXX could be anything. Another one is a POST request where we call /module and pass some data to be processed.

The interesting thing that the XXX gets passed to the module, in our case let us consider that to tbe the HRService as a Future call. You can read about Futures on the Knoldus blog here.

We are handling the Future as a succcess or failure as

private def processFutureResult(future: Future[Any], ctx: RequestContext) = {
    future.mapTo[Option[String]] onComplete {
      case Success(data) => {
        ctx.complete(data.getOrElse("Server is not talking!"))
      }
      case Failure(data) => ctx.complete("Failure in getting results")
    }
  }

Let us take a quick look at the HRService as well

trait HumanResources {
  def startShiftRoutine: Boolean
}

class HumanResourcesService extends Actor with HumanResources {
  val logger = LoggerFactory.getLogger(this.getClass().getName())

  def receive = {
    // Do some brain crunching calculation  
    case "BANG" =>
      startShiftRoutine; sender ! Some("Hello")
    // Default case
    case _ => logger.error("I did not understand the message that you sent!"); sender ! Some("Bad Message")
  }

  def startShiftRoutine: Boolean = { Thread.sleep(2000); true }

}

So keeping it simple, the HumanResourcesService reacts when it gets the BANG message. At other time it spits out the “Bad Message” string.

The point to note here is that our REST Api is not blocked when we are asking the HumanResourcesService to work for us. REST service spins up a future and waits for the future to either succeed or fail.

For testing, we are using the spray.testkit.Specs2RouteTest which makes testing spring so much easier.

class HubRoutesTest extends Specification with Specs2RouteTest with HubRoutes {

  def actorRefFactory = system
  implicit val routeTestTimeout = RouteTestTimeout(timeout.duration)

  "HubRouter" should {

    "Testing Hub Router for happy flow" in {
      Get("/module/BANG") ~> hubRoutes ~> check {
        responseAs[String] must contain("Hello")
      }
    }
    "Testing Hub Router for alternate flow" in {
      Get("/module/anything") ~> hubRoutes ~> check {
        responseAs[String] must contain("Bad")
      }
    }
     "Testing Hub Router for POST flow" in {
      Post("/module", "Some Value") ~> hubRoutes ~> check {
        responseAs[String] must contain("Currently I cannot")
      }
    }
  }
}

Hence, you would notice that for GET request, in /module/BANG we get back a Hello response. For anything else, we get back a “Bad Message” response. For the post call, which we have not implemented right now, we get back the “Currently I cannot process data with” response.

The entire starter kit is present on the Knoldus github account. You would like to add test cases for your real world Akka actor testing. More details here.

Advertisements

About Vikas Hazrati

Vikas is the Founding Partner @ Knoldus which is a group of software industry veterans who have joined hands to add value to the art of software development. Knoldus does niche Reactive and Big Data product development on Scala, Spark and Functional Java. Knoldus has a strong focus on software craftsmanship which ensures high-quality software development. It partners with the best in the industry like Lightbend (Scala Ecosystem), Databricks (Spark Ecosystem), Confluent (Kafka) and Datastax (Cassandra). To know more, send a mail to hello@knoldus.com or visit www.knoldus.com
This entry was posted in Architecture, Scala and tagged , , , , . Bookmark the permalink.

One Response to Spray with Akka Starter Kit

  1. Marco Tinman says:

    Am getting an error could not find implicit value for parameter timeout: akka.util.Timeout

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s