テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

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のユーザであれば拒否反応は比較的少ないはずだ. ただし, 関数的な書き方は完全に強制される. ドキュメントが一応あるので読むといい. モナドがどうとかいうことばかり書いてあるから.

広告を非表示にする