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がさらに便利に!