テストステ論

京大卒の高テス協会会長がテストステロンに関する情報をお届けします

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

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のユーザは、

  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]にすることが処理の概念的な説明だが、このために、以下のような関数が提供されている。

  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という魔物も使っており、明らかに簡単には読めない)

finagle/finch - Gitter

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