akka-httpベースでS3を実装することを完全に断念した私は今, finchの勉強をしている. この記事では, finchというのは一体何なのか?ということをすごくざっくりと説明する.
finchはfinagle-httpの上に作られた, RESTを関数的に書くことを助けるフレームワークである.
finagle-httpとは?
まずfinagle(発音: ふぃねーごー)は, protocol-agnosticな非同期RPCフレームワークである. protocol-agnosticというのは日本語でいうと, 「通信プロトコルがどういうものであるかは抽象化されている」ということである. agnosticというのは, コンピュータシステムの世界ではよく使われる言葉である. もっと簡単な言葉でいうと, unwareである.
従ってfinagle-httpとは, protocol=HTTPとなっているfinagleのことである.
finchは, finagle-http, つまり, Service[Request, Response]の世界を作り出すためのフレームワークである.
型で書くと,
finagle.http.Request -> finch.Input -> finch.Endpoint -> finch.Output -> finagle.http.Response
である. ざっくりいうと, 真ん中のみっつを, Endpoint#toServiceによってServiceに持ち上げる力を持つ.
toServiceを担うのは, ToService traitである. ToService traitは, ざっくりいうと, 合成された(Coproductな)EndpointからServiceを作り出す力を持つ.
trait ToService[A] { def apply(endpoint: Endpoint[A]): Service[Request, Response] }
Endpointは型を持つ. 例えば, Endpoint[A]と表記される. これは, 「そのEndpointはA型の結果を返す」という意味である. 例えば, Ok("Hello World")なるものを返すEndpointはEndpoint[String]型を持つ.
CoproductなEndpointはEndpoint[A :+: B]のような型を持つ. これは例えば, /aにアクセスすればAを返すし, /bにアクセスすればBを返すという感じである.
ToServiceはここからServiceを作る. Coproductに関するその処理は, 結果的に, A => Byteを作る関数を使う.
implicit def coproductRouterToService[C <: Coproduct](implicit folder: Folder.Aux[EncodeAll.type, C, Response] ): ToService[C] = new ToService[C] { def apply(router: Endpoint[C]): Service[Request, Response] = endpointToService(router.map(folder(_))) }
これをEncodeResponseという. (もっと詳しくは, AをResponseにして統一することが目標で, そのためにEncodeResponseを使うということ)
trait EncodeResponse[-A] { def apply(rep: A): Buf def contentType: String def charset: Option[String] = Some("utf-8") }
ここまでの情報で, finchのユーザは,
- 小さなEndpointを作る.
- Endpointを合成する
- 各Endpointの返り値についてEncodeResponseをscopeに入れる
- toServiceを呼び出す
ことで使うことが出来る.
しかし, finch.Outputは0.9.1の現在, 正格である. これは, streamingの値を返せないことを意味する.
このためのworkaroundとして, finchは, EndpointがdirectにResponseを返すことも許容する. つまり, Ok(A)ではなく, Ok(finagle.http.Response)を返せるということである.
この時, Coproductは例として, Endpoint[A :+: Response]となる. これをEndpoint[Response :+: Response]にすることが処理の概念的な説明だが, このために, 以下のような関数が提供されている.
protected object EncodeAll extends Poly1 { /** * Transforms a [[Response]] directly into a constant service. */ implicit def response: Case.Aux[Response, Response] = at(r => r) /** * Transforms an encodeable value into a constant service. */ implicit def encodeable[A: EncodeResponse]: Case.Aux[A, Response] = at(a => encodeResponse(a))
まとめ
この数日間, finagle/finchを調査している. 簡単なRESTサーバは実装出来ることが分かったが, S3をきれいに(もちろん高性能に)実装出来るかは, もう少し検討が必要そうだ.
finchは, Gitterチャンネルがあり, ここで質問すれば分からないことも分かるようになる. (しかし主要開発者の人は頭が良すぎるのか, 読めば簡単に分かるくらいの感じで来る. しかし, コードを見て分かるとおり, 高度に関数的であり, shapelessという魔物も使っており, 明らかに簡単には読めない)
finchは0.9.1で, 1.0への着陸準備に入っているライブラリであり, 機能としては揃っている印象がある. 設計としては, sprayに近く(shapelessを使ってる点もね), sprayのユーザであれば拒否反応は比較的少ないはずだ. ただし, 関数的な書き方は完全に強制される. ドキュメントが一応あるので読むといい. モナドがどうとかいうことばかり書いてあるから.