Effective Spray - Best way to arrange your routes

Reading time ~1 minute

Spray is an elegant Framework in many ways. Here is my opinion on how to arrange your controllers(actions) in your projects, so that you can easily maintain them and test them.

Project structure

UserService_scala_-__badboy-api__-_badboy-api_-____Documents_projects_badboy-api_.png

My Spray project structure.

Controllers

You will find that I didn’t use class to extend Actor. I only use trait in these files.

package com.yours.services

import spray.routing._
import spray.http._

// this trait defines our service behavior independently from the service actor
trait PreferenceService extends HttpService {
  val preferenceRoute =
    path("prefer") {
      get {
        complete("ok")
      }
    }
}
package com.yours.services

import spray.routing._
import spray.http._

// this trait defines our service behavior independently from the service actor
trait PreferenceService extends HttpService {
  val preferenceRoute =
    path("prefer") {
      get {
        complete("ok")
      }
    }
}

Routes

Here is your route actor, which will combine all the controller traits.

package com.yours

import akka.actor.{ActorRefFactory, Actor}
import com.yours.services.{UserService, FeedService, PreferenceService}

/**
 * Created by visualskyrim on 12/25/14.
 */
class RoutesActor extends Actor with Routes {
  override val actorRefFactory: ActorRefFactory = context
  //def receive = runRoute(boyRoute ~ feedRoute ~ preferenceRoute)
  def receive = runRoute(routes)
}

trait Routes extends UserService with FeedService with PreferenceService {
  val routes = {
    userRoute ~
    feedRoute ~
    preferenceRoute
  }
}

Boot

This part is like Global in Play framework, used to start your application. We launch our route actor here.

import akka.actor.{ActorSystem, Props}
import akka.io.IO
import com.yours.utils.db.DbSupport
import com.yours.RoutesActor
import spray.can.Http
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._

import com.yours.utils.DynamodbSupport

object Boot extends App with DbSupport with DynamodbSupport {
  implicit val system = ActorSystem("your-api")

  val service = system.actorOf(Props[RoutesActor], "your-service")

  implicit val timeout = Timeout(5.seconds)
  IO(Http) ? Http.Bind(service, interface = "localhost", port = 8080)
  // initialize the helpers
  // initialize the Db
  initTables()
  initDynamoDbTables()
}

Benefits

I believe this arrangement fits most cases.

  • Most people would like request handler to be separated in several files, grouping the controllers according to the related features. If people want to change anything or add anything, they can easily locate the place.
  • It’s clean. There will be no association with the Akka system in the middle of your real logic.
  • You might have noticed that since we use trait to build controllers, we can test our controllers without getting hands dirty with Akka.