finchxで遊ぼう

Scala Advent Calendar 2018 の 11 日目です

finch v0.25.0からEndpointに
Twitter Futureではなくcats Effect型を使えるようになりました。
[公式サイト](https://github.com/finagle/finch#cats-effect-and-twitter-futures) をみると

> *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とほぼ変わらない記述です。

```scala
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で使わないパターンです。

```scala
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にしています。

```scala
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で使用時に握りつぶしています。

```scala
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にして使ってみます。

```scala
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がさらに便利に！