LoginSignup
2
0

More than 5 years have passed since last update.

Akka+AkkaHttp+SlickでTODOアプリAPI(その4)

Last updated at Posted at 2018-04-12

始めに

前回に引き続き、今回は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化を行います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0