finch触ってみた

[finch](https://github.com/finagle/finch)は関数型なFinagleの薄いラッパー。CatsやShapelessを使っている。

なにやら速いらしい。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr">Impressive results by <a href="https://twitter.com/hashtag/Finch?src=hash">#Finch</a> (now <a href="https://twitter.com/hashtag/Scala?src=hash">#Scala</a> 2nd fastest HTTP library) running <a href="https://twitter.com/techempower">@techempower</a> benchmark (430k QPS peak): <a href="https://t.co/YeBMnJeQ5W">pic.twitter.com/YeBMnJeQ5W</a></p>&mdash; Vladimir Kostyukov (@vkostyukov) <a href="https://twitter.com/vkostyukov/status/703374308056309760">2016年2月27日</a></blockquote>

なぜ速いのか?
http://vkostyukov.net/posts/how-fast-is-finch/

今回は触ってみただけなので、速いことより使い勝手をみていくことにする。


## Hello World

```scala
import io.finch._
import com.twitter.finagle.Http

val api: Endpoint[String] = get("hello") { Ok("Hello, World!") }

Http.serve(":8080", api.toService)
```

READMEに載ってるやつ。これで`sbt run`して`http://localhost:8080/hello`にアクセスできる。


## Parameter

GETやPOSTでのパラメータの受け取りの例。

```scala:パラメータを受け取る例
// パスの一部として取得
// GET /hello/:id
get("hello" :: int) { id: Int =>
  ...
}

// クエリパラメータとして取得
// GET /hello/:id?name={string}
get("hello" :: int :: param("name")) { (id: Int, name: String) =>
  ...
}

// Intで取得
// GET /hello?id={int}
get("hello" :: param("id").as[Int]) { id: Int =>
  ...
}

// case classに詰めて取得
// GET /hello?a={string}&b={string}
case class Foo(a: String, b: String)

val ab: Endpoint[String :: String :: HNil] = param("a") :: param("b")

get("hello" :: ab.as[Foo]) { foo: Foo =>
  ...
}
```

Endpointを組み合わせると、HListで表現するのでGenericでcase classに相互変換可能と。


## Validation

数と文字数の制限しか用意されてないもよう。[ValidationRules](https://github.com/finagle/finch/blob/95ba9face87c675a7ea52e8258eeef45e2721c9f/core/src/main/scala/io/finch/ValidationRules.scala)

```scala
// GET /hello?a={string}
val a = param("a").should(beShorterThan(5)) // aの長さ < 5

get("hello" :: a) { foo: Foo =>
  ...
}
```

独自定義も簡単に書ける。

```scala
val a = param("a").should("a should be a")(_ == "a")
```

```scala
val aRule = ValidationRule[String]("a should be a")(_ == "a")
val a = param("a").should(aRule)
```

and/orで組み合わせる。

```scala
val aRule = ValidationRule[String]("a should be a")(_ == "a") or ValidationRule[String]("a should length 3")(_.length == 3)
```

cookieやheader等も同様。

```scala:cookieの取得
val cookieUid = cookie("uid").should("uid invalid format")(_.value.length == 9)

get("hello" :: cookieUid) { cc: Cookie =>
  ...
}
```

そういえばforも。

```scala
val x: Endpoint[(String, String)] = for {
  a <- param("a")
  b <- cookie("b")
} yield (a, b.value)
```

とりあえずplayのFormより全然よさそう...

とにかくEndpointになるので、組み合わせやすい。sprayのDirectiveっぽさがある。


## Derive

自動導出してくれてるのでcase classごとのencode/decodeの定義が不要。

jsonを受け取ってjsonで返す例。

```scala
import io.finch.circe._
import io.circe.generic.auto._

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

// POST /hello
// {"id":"int", "name":"name"}
post("hello" :: body.as[User]) { user: User =>
  Ok(user)
}
```

case classそのまま渡せばjsonにしてくれるので便利。


## Routing

Endpointを組み合わせていく。

```scala
val hello: Endpoint[String] = get("hello") { Ok("hello") }
val world = get("world") { Ok("world") }
val hw = hello :+: world
```

さらに組み合わせて最終的に1つに。

```scala
val foo = get("foo") { Ok("foo") }
val bar = get("bar") { Ok("bar") }
val fb = foo :+: bar
val route: Endpoint[String :+: String :+: String :+: String :+: CNil] = hw :+: fb
```

型がネストしてしまうが:+:するときにフラットにしてくれてる。
これがしたいために[shapelessにクラス追加](https://github.com/milessabin/shapeless/pull/414)してるので気合いが入ってる。


## おわり

Endpointが組み合わせやすく、型安全だし再利用性も高そう。
あと、あくまでfinagleがベースなのでsqlとか書くにしてもfinagle-mysqlとか使わないと遅くなったりしそう。

興味あれば[ドキュメント](https://github.com/finagle/finch/tree/master/docs)を読むといいと思われます。( ･∀･)ﾉｼ