finchの紹介

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を作り出す力を持つ。

1
2
3
trait ToService[A] {
  def apply(endpoint: Endpoint[A]): Service[RequestResponse]
}

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を作る関数を使う。

1
2
3
4
5
6
  implicit def coproductRouterToService[C <: Coproduct](implicit
    folder: Folder.Aux[EncodeAll.typeCResponse]
  ): ToService[C] = new ToService[C] {
    def apply(router: Endpoint[C]): Service[RequestResponse] =
      endpointToService(router.map(folder(_)))
  }

これをEncodeResponseという。(もっと詳しくは、AをResponseにして統一することが目標で、そのためにEncodeResponseを使うということ)

1
2
3
4
5
trait EncodeResponse[-A] {
  def apply(rep: A): Buf
  def contentType: String
  def charset: Option[String] = Some("utf-8")
}

ここまでの情報で、finchのユーザは、

  1. 小さなEndpointを作る。
  2. Endpointを合成する
  3. 各Endpointの返り値についてEncodeResponseをscopeに入れる
  4. 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]にすることが処理の概念的な説明だが、このために、以下のような関数が提供されている。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  protected object EncodeAll extends Poly1 {
    /**
     * Transforms a [[Response]] directly into a constant service。
     */
    implicit def response: Case.Aux[ResponseResponse] =
      at(r => r)

    /**
     * Transforms an encodeable value into a constant service。
     */
    implicit def encodeable[A: EncodeResponse]: Case.Aux[AResponse] =
      at(a => encodeResponse(a))

まとめる。

この数日間、finagle/finchを調査している。簡単なRESTサーバは実装出来ることが分かったが、S3をきれいに(もちろん高性能に)実装出来るかは、もう少し検討が必要そうだ。

finchは、Gitterチャンネルがあり、ここで質問すれば分からないことも分かるようになる。(しかし主要開発者の人は頭が良すぎるのか、読めば簡単に分かるくらいの感じで来る。しかし、コードを見て分かるとおり、高度に関数的であり、shapelessという魔物も使っており、明らかに簡単には読めない)

finagle/finch - Gitter

finchは0.9.1で、1.0への着陸準備に入っているライブラリであり、機能としては揃っている印象がある。設計としては、sprayに近く(shapelessを使ってる点もね)、sprayのユーザであれば拒否反応は比較的少ないはずだ。ただし、関数的な書き方は完全に強制される。ドキュメントが一応あるので読むといい。モナドがどうとかいうことばかり書いてあるから。

comments powered by Disqus
Built with Hugo
テーマ StackJimmy によって設計されています。