始めに
前回に引き続き、今回はAPIルート部分を作成します。
構成##
- IntelliJ 2018
- Scala 2.12.5
- sbt 1.1.4
- Akka 2.5.11
- AkkaHttp 10.1.0
- Slick 3.2.3
- Scalaz 7.2.19
成果物##
APIルート
まずはTodoActorのコマンドをJson変換をする必要があるのでシリアライザーを定義します。詳しくはドキュメントを参照してください。
TodoActorJsonSerializer.scala
package todo.api.actor
import spray.json._
trait TodoActorJsonSerializer extends DefaultJsonProtocol {
import TodoActor._
implicit val findByIdCommand: RootJsonFormat[FindByIdCommand] = jsonFormat1(FindByIdCommand)
implicit val createCommand: RootJsonFormat[CreateCommand] = jsonFormat1(CreateCommand)
implicit val updateCommand: RootJsonFormat[UpdateCommand] = jsonFormat2(UpdateCommand)
implicit val deleteCommand: RootJsonFormat[DeleteCommand] = jsonFormat1(DeleteCommand)
implicit val todoReplyFormat: RootJsonFormat[TodoReply] = jsonFormat2(TodoReply)
implicit val createdReply: RootJsonFormat[CreatedReply] = jsonFormat1(CreatedReply)
}
続いてルートを定義します。
せっかくなのでFindByIdの結果がNoneだったらNotFoundを返すようにします。
また、例外を捕捉してエラーを返すようにExceptionHandlerを定義しています。
TodoRoute.scala
package todo.api
import scala.util.control.NonFatal
import akka.actor.ActorRef
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.{ExceptionHandler, Route}
import akka.pattern.ask
import akka.util.Timeout
import todo.api.actor.TodoActorJsonSerializer
object TodoRoutes {
def apply(todoActor: ActorRef)(implicit timeout: Timeout): TodoRoutes = new TodoRoutes(todoActor)
}
// TodoActorをインジェクション
class TodoRoutes(todoActor: ActorRef)(implicit timeout: Timeout)
extends SprayJsonSupport
with TodoActorJsonSerializer {
import todo.api.actor.TodoActor._
implicit def todoExceptionHandler: ExceptionHandler = ExceptionHandler {
case NonFatal(ex) =>
extractLog { implicit log =>
extractUri { uri =>
log.error(s"raised error!! uri: $uri, reason: ${ex.getMessage}")
complete(
StatusCodes.InternalServerError -> s"raised error!! uri: $uri, reason: ${ex.getMessage}")
}
}
}
val routes: Route = handleExceptions(todoExceptionHandler) {
extractLog { implicit log =>
extractUri { uri =>
extractMethod { method =>
log.info(s"call api. method: ${method.value}, uri: $uri")
pathPrefix("todos") {
pathEndOrSingleSlash {
get {
onSuccess((todoActor ? FindAllCommand).mapTo[Seq[TodoReply]]) { res =>
complete(StatusCodes.OK -> res)
}
} ~
post {
entity(as[CreateCommand]) { req =>
onSuccess((todoActor ? req).mapTo[CreatedReply]) { res =>
complete(StatusCodes.OK -> res)
}
}
}
} ~
path(IntNumber) { id =>
get {
onSuccess((todoActor ? FindByIdCommand(id)).mapTo[Option[TodoReply]]) {
case Some(todoReply) =>
complete(StatusCodes.OK -> todoReply)
case None =>
complete(StatusCodes.NotFound)
}
} ~
put {
entity(as[UpdateCommand]) { req =>
onSuccess(todoActor ? req) { _ =>
complete(StatusCodes.NoContent)
}
}
} ~
delete {
onSuccess(todoActor ? DeleteCommand(id)) { _ =>
complete(StatusCodes.NoContent)
}
}
}
}
}
}
}
}
}
加えて今回はHttpAppを使ってみたいのでサーバーを定義します。
TodoAPIServer.scala
package todo.api
import scala.concurrent.Await
import scala.concurrent.duration._
import scala.util.Try
import akka.Done
import akka.actor.ActorSystem
import akka.http.scaladsl.server.{HttpApp, Route}
object TodoAPIServer {
def apply(todoRoutes: TodoRoutes): TodoAPIServer = new TodoAPIServer(todoRoutes)
}
// TodoRoutesをインジェクション
class TodoAPIServer(todoRoutes: TodoRoutes) extends HttpApp {
override protected val routes: Route = todoRoutes.routes
override protected def postServerShutdown(attempt: Try[Done], system: ActorSystem): Unit = {
super.postServerShutdown(attempt, system)
system.terminate()
Await.result(system.whenTerminated, 30.seconds)
()
}
}
以上でActorのコマンドの扱いがなんだか微妙ですが、APIルートは完成です。
終わりに
次回は仕上げにエントリーポイントを定義し、DockerImage化を行います。