始めに

前回に引き続き、今回は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

成果物

https://github.com/lightstaff/scala-todo-api

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化を行います。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.