5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Scala Advent Calendar 2018 の 11 日目です

finch v0.25.0からEndpointに
Twitter Futureではなくcats Effect型を使えるようになりました。
公式サイト をみると

Starting with 0.25, Finch artifacts are published for both Twitter Futures (Endpoint[A]) and Cats Effects (Endpoint[F[_], A]):

  • Use finch-* artifacts for endpoints locked in Twitter Futures (legacy)
  • Use finchx-* artifacts for polymorphic endpoints (recommended)

と記載があります。
recommendedを使いたいですよね。
それではfinchxを使ってみましょう!

IOを使う

こちらはいままでのTwitterFutureとほぼ変わらない記述です。

import cats.effect._
import cats.implicits._
import com.twitter.finagle.Http
import com.twitter.server.TwitterServer
import com.twitter.util.Await
import io.circe.generic.auto._
import io.finch._
import io.finch.circe._

case class User(id: Int, name: String)

trait MixInApiIO {
  private[this] val cache: Map[Int, User] = Map(
    1 -> User(1, "hoge"),
    2 -> User(2, "fuga"),
    3 -> User(3, "piyo")
  )

  def selectUsers(): IO[Seq[User]] =
    IO(cache.values.toSeq)
}

object Main 
    extends TwitterServer
    with Endpoint.Module[IO]
    with MixInApiIO {
  def main(): Unit = {
    val endpoint = get("users") {
      selectUsers().map(Ok)
    }
    val server = Http.serve(":8080", endpoint.toService)
    onExit(server.close())
    Await.result(server)
  }
}

EitherTをつかう

ビジネスロジックでエラー情報を格納し返していますが
Endpointで使わないパターンです。

import cats.data._
import cats.effect._
import cats.implicits._
import com.twitter.finagle.Http
import com.twitter.server.TwitterServer
import com.twitter.util.Await
import io.circe.generic.auto._
import io.finch._
import io.finch.circe._

case class User(id: Int, name: String)

trait MixInApiEitherTIO {
  private[this] val cache: Map[Int, User] = Map(
    1 -> User(1, "hoge"),
    2 -> User(2, "fuga"),
    3 -> User(3, "piyo")
  )
  def selectUser(id: Int): EitherT[IO, Throwable, User] =
    EitherT.fromOption[IO](cache.get(id), new NoSuchElementException(s"$id is not exists."))
}

object Main
    extends TwitterServer
    with Endpoint.Module[({ type l[a] = EitherT[IO, Throwable, a] })#l]
    with MixInApiEitherTIO {

  def main(): Unit = {
    val endpoint =
      get("users" :: path[Int]) { id: Int =>
        selectUser(id).map(Ok)
      }

    val server = Http.serve(":8080", endpoint.toService)
    onExit(server.close())
    Await.result(server)
  }
}

EitherTを剥がしてIOにして使う

ビジネスロジックでエラー情報を格納してもらっているので
EndpointでIOにする際に、LeftはBadRequest、RightはOkにしています。

import cats.data._
import cats.effect._
import cats.implicits._
import com.twitter.finagle.Http
import com.twitter.server.TwitterServer
import com.twitter.util.Await
import io.circe.generic.auto._
import io.finch._
import io.finch.circe._

case class User(id: Int, name: String)

trait MixInApiEitherTIO {
  private[this] val cache: Map[Int, User] = Map(
    1 -> User(1, "hoge"),
    2 -> User(2, "fuga"),
    3 -> User(3, "piyo")
  )
  def selectUser(id: Int): EitherT[IO, Throwable, User] =
    EitherT.fromOption[IO](cache.get(id), new NoSuchElementException(s"$id is not exists."))
}

object Main
    extends TwitterServer
    with Endpoint.Module[IO]
    with MixInApiEitherTIO {

  def main(): Unit = {
    val endpoint =
      get("users" :: path[Int]) { id: Int =>
        selectUser(id).value.map(_.fold({
          case e: Exception => BadRequest(e)
          case e            => InternalServerError(new Exception(e))
        }, Ok))
      }

    val server = Http.serve(":8080", endpoint.toService)
    onExit(server.close())
    Await.result(server)
  }
}

WriterTを使う

このパターンではせっかくビジネスロジックでログ出力をしているのに
Endpointで使用時に握りつぶしています。

import cats.data._
import cats.effect._
import cats.implicits._
import com.twitter.finagle.Http
import com.twitter.server.TwitterServer
import com.twitter.util.Await
import io.circe.generic.auto._
import io.finch._
import io.finch.circe._

case class User(id: Int, name: String)

trait MixInApiWriterTIO {
  private[this] val cache: Map[Int, User] = Map(
    1 -> User(1, "hoge"),
    2 -> User(2, "fuga"),
    3 -> User(3, "piyo")
  )
  def selectUsers(): WriterT[IO, List[String], Seq[User]] =
    for {
      _     <- WriterT.tell[IO, List[String]](List("select start."))
      users <- WriterT.liftF[IO, List[String], Seq[User]](IO(cache.values.toSeq))
      _     <- WriterT.tell[IO, List[String]](List(s"user count: ${users.size}"))
    } yield users
}

object Main
    extends TwitterServer
    with Endpoint.Module[({ type l[a] = WriterT[IO, List[String], a] })#l]
    with MixInApiWriterTIO {

  def main(): Unit = {
    val endpoint = get("users") {
      selectUsers().map(Ok)
    }

    val server = Http.serve(":8080", endpoint.toService)
    onExit(server.close())
    Await.result(server)
  }
}

WriterTを剥がしてIOにして使う

せっかくビジネスロジックでログを書いてくれているのに握りつぶしてしまっては勿体無いですよね。
ではWriterTを剥がしてログ出力をし、IOにして使ってみます。

import cats.data._
import cats.effect._
import cats.implicits._
import com.twitter.finagle.Http
import com.twitter.server.TwitterServer
import com.twitter.util.Await
import io.circe.generic.auto._
import io.finch._
import io.finch.circe._

case class User(id: Int, name: String)

trait MixInApiWriterTIO {
  private[this] val cache: Map[Int, User] = Map(
    1 -> User(1, "hoge"),
    2 -> User(2, "fuga"),
    3 -> User(3, "piyo")
  )
  def selectUsers(): WriterT[IO, List[String], Seq[User]] =
    for {
      _     <- WriterT.tell[IO, List[String]](List("select start."))
      users <- WriterT.liftF[IO, List[String], Seq[User]](IO(cache.values.toSeq))
      _     <- WriterT.tell[IO, List[String]](List(s"user count: ${users.size}"))
    } yield users
}

object Main
    extends TwitterServer
    with Endpoint.Module[IO]
    with MixInApiWriterTIO {

  def main(): Unit = {
    val endpoint = get("users") {
      for {
        res           <- selectUsers().run
        (logs, users) = res
      } yield {
        logs.foreach(println)
        Ok(users)
      }
    }

    val server = Http.serve(":8080", endpoint.toService)
    onExit(server.close())
    Await.result(server)
  }
}

まとめ

polymorphic endpoints、いろいろな使い方ができて便利ですね。
もちろんEffectを実装すればいままで通りTwitterFutureを使うことも可能です。
便利だったfinchがさらに便利に!

5
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
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?