spray-routingにPlay2ライクなコントローラを導入する

皆様、御機嫌よう かとうです。

このブログ記事は、Scala Advent Calendar 2014 23日目の記事です。

最近、sprayを使っているので、簡単なチップスを公開したいと思います。

# sprayとは

Akka ActorベースのREST APIを実装するためのフレームワークです。[Benchmarking spray](http://spray.io/blog/2013-05-24-benchmarking-spray/) によるとJVM最速の部類に入るようです。詳しくは[ここ](http://spray.io/)みてください。

## spray-can

spray-canを利用するとHTTPのリクエスト/レスポンスのハンドリングをActor上で扱えるようになります。実装コード例は以下です(詳細は [こちら](https://github.com/spray/spray/blob/master/examples/spray-can/simple-http-server/src/main/scala/spray/examples/DemoService.scala) を見てください)。HTTPのリクエストをリアクティブにハンドリングできます。


```scala
class DemoService extends Actor with ActorLogging {
// ...
  def receive = {
// ...
    case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
      sender ! HttpResponse(entity = "PONG!")
// ...
```

## spray-routing

spray-routingは、ルーティングやアクションなどをDSLを使って宣言的に記述できる特徴を持っています。

以下は[github上にあるサンプル](https://github.com/spray/spray/tree/release/1.3/examples/spray-routing/on-spray-can/src/main/scala/spray/examples)です。

```scala
object Boot extends App {

  implicit val system = ActorSystem("on-spray-can")

  val service = system.actorOf(Props[DemoServiceActor], "demo-service")

  IO(Http) ! Http.Bind(service, "localhost", port = 8080)

}
```

```scala
class DemoServiceActor extends Actor with DemoService {

  def actorRefFactory = context

  def receive = runRoute(demoRoute)

}

trait DemoService extends HttpService {

  implicit def executionContext = actorRefFactory.dispatcher

  val demoRoute = {
    get {
      pathSingleSlash {
        complete(index)
      } ~
      path("ping") {
        complete("PONG!")
      } ~
      path("stream1") {
        // we detach in order to move the blocking code inside the simpleStringStream into a future
        detach() {
          respondWithMediaType(`text/html`) { // normally Strings are rendered to text/plain, we simply override here
            complete(simpleStringStream)
          }
        }
      } ~
      path("stream2") {
        sendStreamingResponse
      } ~
      path("stream-large-file") {
        encodeResponse(Gzip) {
          getFromFile(largeTempFile)
        }
      } ~
  /** 以下略 */
  }

  /** 以下略 */

}
```

# Play2ライクなコントローラの導入

上記のルーティング情報とアクションをつらつらならべて書くのは、さすがにつらいので、Routeを別々のtraitに記述して後で合成してもよいかもしれませんね。とはいえ、それだとtrait側にルーティング情報も一緒に記述するので、エンドポイントの数が増えると管理が大変ですねってことで、Play2ライクなコントローラが書けるようにしてみました。

やることは簡単で、Route上に処理を記述しないで、別途用意したコントローラクラスに委譲するだけです。

```scala
object Main extends App {

  val userRepository = UserRepository.ofDB

  val serviceActorRef = system.actorOf(Props(ServiceActor(userRepository)))

  IO(Http) ! Http.Bind(serviceActorRef, interface = "127.0.0.1", port = 8080)

}
```

Actor内部でrunRouteに与えるRouteを定義しますが、ここではルーティング情報だけを記述します。リクエストのハンドリング自体はコントローラに委譲させて、コントローラが返すActionから得られた戻り値をレスポンスに変換します(toResponseの部分)。

```scala
case class ServiceActor(userRepository: UserRepository) extends Actor with Routes {

  private val userController = UserController(userRepository)

  private lazy val routes: Route = {
    pathPrefix(apiVersion) {
      path(users) {
        implicit ctx =>
          val startTimeStamp = getQueryParam("startTimeStamp").map(e => TimePoint(e.toLong))
          val maxEntities = getQueryParam("maxEntities").map(_.toInt)
          userController.getUsers(startTimeStamp, maxEntities)(ctx).toResponse()
      }
    }
  }

  def actorRefFactory = context

  def receive = runRoute(routes)

  protected def getQueryParam(key: String)(implicit ctx: RequestContext): Option[String] =
    ctx.request.uri.query.get(key)

  protected def parseEntity[A](implicit ctx: RequestContext, unmarshaller: Unmarshaller[A]) =
    ctx.request.entity.as[A]

  protected implicit def convertToResponseHolder[A](resultTry: Try[A]) = new {

    def toResponse()(implicit ctx: RequestContext, marshaller: ToResponseMarshaller[A]): Unit =
      resultTry.map { result =>
        ctx.complete(result)
      }.get
  }


}
```

コントローラでは、Actionを返すことになります。

```scala
case class UserController(userRepository: UserRepository) {

  def getUsers(startTimeStamp: Option[TimePoint], maxEntities: Option[Int]): Action[EntitiesChunk[UserId, User]] = Action { implicit ctx =>
      userRepository.resolveAllBy(startTimeStamp, maxEntities).map { chunk =>
        EntitiesChunk[UserId, User](chunk)
      }
  }

}
```

```scala
trait Action[R] {

  def apply(request: RequestContext): Try[R]

}

object Action {

  def apply[R](block: RequestContext => Try[R]) = new Action[R] {
    override def apply(ctx: RequestContext): Try[R] = block(ctx)
  }

}
```

# まとめ

この方法であれば、ルーティング情報を集約でき、コントローラにリクエストのハンドリングを委譲できるようになります。

まぁ、こんなことするぐらいなら、Play2でいいじゃんというのはありますが、、、DDDを採用していてそもそもフルスタックフレームが使いづらい(ヘキサゴナルアーキテクチャを想定している)というのがあったのでSprayにしたわけですが、spray-routingを生で使うのはさけたいところですね。

ということで、参考になれば幸いです。