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
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.