finch触ってみた

  • 25
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

finchは関数型なFinagleの薄いラッパー。CatsやShapelessを使っている。

なにやら速いらしい。

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

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

Hello World

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でのパラメータの受け取りの例。

パラメータを受け取る例
// パスの一部として取得
// 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

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

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

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

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

and/orで組み合わせる。

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

cookieやheader等も同様。

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

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

そういえばforも。

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で返す例。

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を組み合わせていく。

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

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

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にクラス追加してるので気合いが入ってる。

おわり

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

興味あればドキュメントを読むといいと思われます。( ・∀・)ノシ