Akka DSL's and Routing In Play with Cake Pattern

Back

Akka DSL and Spray Routing with Cake Pattern

Today I was working my way through some Akka tutorials and was thinking to myself: man, I wish I knew what they called each symbol! So I got to thinking, well, I'm sure others are thinking the same thing, why not write a quick post about it?

One of the first things I found myself wondering when dealing with Akka was: What the heck is Props? It wasn't until I saw this that it made any sense to me:

class Worker extends Actor {
    ...
}

class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, listener: ActorRef) extends Actor {
    ...
}

...

val listener = system.actorOf(Props[Listener], name = "listener")
val master = system.actorOf(Props(new Master(
    nrOfWorkers, nrOfMessages, nrOfElements, listener
)), name = "master")

Specifically, the way that we can do both Props[ActorClass] and Props(new ActorClassWithConstructorArgs(a,b,c)) made me realize that the role of Props was to provide a factory to create Actors. And on wising up and reading the scala doc, it was pretty obvious that when you see Props you're seeing the preferred way to create Actors.

If you look at the scala doc you'll see that Props takes a Deploy object which can be configured from a conf file. Of course, you won't often find a Props object being created without use of the factory. Something like this:

val d = Deploy(path = "/tmp/test.conf")
val p = new Props(deploy = d, classOf[Listener], scala.collection.immutable.Seq.empty[Any])
val s = ActorSystem()
val a = s.actorOf(p)

Is less desireable than:

val s = ActorSystem()
s.actorOf(Props[Listener].withDeploy(Deploy(path="/tmp/test.conf")))

Because it's a bit easier to read and understand the factory methods.

So what's this ! function? It's defined in the ActorRef docs, and does the same thing as the tell function, but with an implicit sender instead of an explicit:

a ! PiApproxiation(1, Duration("1 second")

is the same as

a tell (PiApproxiation(1, Duration("1 second")), a)

The same can be said of the ? function, which corresponds to the ask function. Defined in AskableActorRef. The example code from the Akka tutorials was simple enough to understand once the symbols were resolved. While I like scala's ability to provide very unique function names and create readable / english-like code, it does make it harder to search sometimes.

The official documentation is the best place to read and learn about the various components that make up Akka, so it's well worth a look. The other thing I noticed, is that in most tutorials involving Spray and a Rest Service, there is only ever a single routing trait setup, and not an enterprise version that has the routes seperated by their concerns and then mixed together via the Cake pattern.

First off, let's create a simple Spray application to show case this pattern. Let's say that we have one endpoint that responds to /beef/ and another that responds to /nog/. Obviously we don't want to mix any routes that are specific to beef or nog, as that would be kind of gross. So we'll need to create a couple things, first, a DummyActor:

object DummyActor {
    case class Process(s: String) 
}

class DummyActor(requestContext: RequestContext) extends Actor {
    def receive = {
        case DummyActor.Process(s) => 
            requestContext.complete(s)
            context.stop(self)
    }
}

This actor really doesn't do much besides spits back the string that it was given, but you can imagine that in your own cases this could call out to services, perform business logic, or do calculations.

Next, let's talk about a simple controller for the pathing and what to do when we receive a request:

trait Beef extends HttpService {
val beefRoutes = 
    pathPrefix("beef") {
        path("cows"){
            pathEnd { 
                respondWithMediaType(`text/plain`) {
                    requestContext => {
                        val dummyService = actorRefFactory.actorOf(Props(new DummyActor(requestContext)))
                        dummyService ! DummyActor.Process("cows")
                    }
                }
            }
        } ~
        path("bulls") {
            respondWithMediaType(`text/plain`) {
                requestContext => {
                    val dummyService = actorRefFactory.actorOf(Props(new DummyActor(requestContext)))
                    dummyService ! DummyActor.Process("bulls")
                }
            }
        }
    }  
}

This uses the spray routing DSL to define the paths /beef/cows and /beef/bulls which simply ask our DummyActor to process a string specific to that endpoint (so we can tell things are working). The tilde between the path(...){ pieces concatenates the routes together. For our second endpoint, we'll have something to do with nog:

trait Nog extends HttpService {
    val nogRoutes = 
        pathPrefix("nog") {
            path("egg") {
                respondWithMediaType(`text/plain`) {
                    requestContext => {
                        val dummyService = actorRefFactory.actorOf(Props(new DummyActor(requestContext)))
                        dummyService ! DummyActor.Process("eggnog!")
                    }
                }
            }
        }
}

Similar to the beef trait, this one defines a path for /nog/egg which will return the string eggnog! when matched. The next step after this is to use these routes! Typically, in a Spray application you'll see something like this:

class HttpApp extends Actor with SomeTraitDefiningRoute {
    override val actorRefFactory: ActorRefFactory = context
    def receive = runRoute(route)
}

We have two different traits to be mixed in, but runRoute will only take one route DSL! So how do we do it? Well, if you recall that we had two routes in the Beef trait connected by ~, it may not surprise you to find out we can use this to join multiple route DSL's. To make our lives easier later on and for clarity, we'll create a new trait that does this:

trait RouteService extends HttpService 
with Beef
with Nog
{ 
    val route = {
        beefRoutes ~
        nogRoutes
    }
}

Then all we have to do to have an actor run both beef and nog routes is:

class HttpApp extends Actor with RouteService {
    override val actorRefFactory: ActorRefFactory = context
    def receive = runRoute(route)
}

Pretty simple right? Lastly, to actually have this Actor do something we'll need to bind it via spray's http libraries:

object HttpApp extends App {
    runserver(host=  "localhost", port = 8089)

    def runserver(host: String, port: Int) {
        implicit lazy val system = ActorSystem("HttpSystem")
        sys.addShutdownHook(system.shutdown())

        val httpActor = system.actorOf(Props[HttpApp], name = "httpActor")
        implicit val timeout = Timeout(5.seconds)

        IO(Http) ? Http.Bind(httpActor, interface = host, port = port)
    }
}

This simply defines an object which extends App, therefore inherits a main method. We setup a shutdown hook so that when the JVM goes offline so does our server. Then we bind the actor to the ports specified by our call to runserver.

For clarity, here's the full code including the import statements:

And my build.sbt file looked like this for the project:

name := "Akka Tutorial"

version := "1.0"

scalaVersion := "2.10.4"

resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"

libraryDependencies ++= {
  val sprayVersion = "1.3.1"
  val akkaVersion = "2.3.11"
  Seq(
  "io.spray" % "spray-can" % sprayVersion,
  "io.spray" % "spray-routing" % sprayVersion,
  "io.spray" % "spray-testkit" % sprayVersion,
  "io.spray" % "spray-client" % sprayVersion,
  "io.spray" %%  "spray-json" % "1.2.5",
  "com.typesafe.akka" %% "akka-actor" % akkaVersion,
  "com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
  "com.typesafe.akka" %% "akka-testkit" % akkaVersion % "test",
  "ch.qos.logback" % "logback-classic" % "1.0.12",
  "org.scalatest" %% "scalatest" % "2.0.M7" % "test",
  "com.typesafe" % "config" % "1.2.1"
  )
}

This is a simple introduction to using spray, scala, and Akka together to create a simple skeleton one can easily fill out as they go along. In an actual application it would be best to extract each seperate service (Beef and Nog) to their own files or packages as neccesary. Then use the traits to bind each together. This has numerous advantages, including:

  1. Easier for new team members to grok your code
  2. Easier for you to remember where things are
  3. Easier to test since each piece is seperated until glued together by traits

Hope this has wetted your appetite for playing with some of the cooler libraries and frameworks out there for Scala!

Other Posts

comments powered by Disqus