LoginSignup
25

More than 5 years have passed since last update.

finch触ってみた

Posted at

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とか使わないと遅くなったりしそう。

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

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
25