Finchライブラリの歴史は約1年前にConfettinの地下で始まり、そこでFinagleで REST APIを作成しようとしました 。 finagle-http自体は非常に優れたツールであるという事実にもかかわらず、より豊かな抽象化の深刻な不足を感じ始めました。 さらに、これらの抽象化には特別な要件がありました。 それらは不変で、簡単に構成可能で、同時に非常に単純であると想定されていました。 関数としてシンプル。 そこで、Finchライブラリが登場しました。これは、finagle-httpの上にある非常に薄い関数と型の層であり、finagle-httpでのHTTP(マイクロ|ナノ)サービスの開発をより楽しく簡単にします。
6か月前、ライブラリの最初の安定バージョンがリリースされ、先日、バージョン0.5.0がリリースされました 。 この間、 6社 (うち3 社はまだ公式リストにありません: Mesosphere 、 Shponic 、 Globo.com )がFinchの生産を開始し、そのうちのいくつかは積極的な貢献者にさえなりました。
この投稿では、Finchが構築する3つの柱、
Router
、
RequestReader
および
ResponseBuilder
ます。
Router
io.finch.route
パッケージは、ルートコンビ
io.finch.route
APIを実装します。これにより、ボックスから利用可能なプリミティブルーターからルーターを組み合わせて、無限の数のルーターを構築できます。 Parser Combinatorsとscodecは同じアプローチを使用します。
ある意味では、
Router[A]
は機能
Route => Option[(Route, A)]
です。
Router
は抽象ルート
Route
を取り、残りのルートと取得したタイプ
A
値から
Option
を返します
A
つまり、
Router
は、成功した場合(要求をルーティングできる場合
Some(...)
、
Some(...)
返します。
基本的なルーターは、
int
、
long
、
string
、および
boolean
4つだけです。 さらに、ルートから値を抽出せず、単純にサンプルにマッピングするルーターがあります(たとえば、HTTPメソッドのルーター:
Get
、
Post
)。
次の例は、ルーターを構成するためのAPIを示しています。 ルーター
router
は、フォーム
GET /(users|user)/:id
リクエストをルーティングし、ルートから整数
id
値を抽出します。 演算子
/
(または
andThen
)に注意してください。この助けを借りて、2つのルーターと演算子
|
(または
orElse
)。これにより、論理
orElse
or
。
val router: Router[Int] => Get / ("users" | "user") / int("id")
ルーターが複数の値を抽出する必要がある場合は、特別なタイプ
/
使用できます。
case class Ticket(userId: Int, ticketId: Int) val r0: Router[Int / Int] = Get / "users" / int / "tickets" / int val r1: Router[Ticket] = r0 map { case a / b => Ticket(a, b) }
ルートからサービス(Finagle
Service
)を抽出する特別なタイプのルーターがあります。 これらのルーターはエンドポイントと呼ばれます(実際、
Endpoint[Req, Rep]
は
Router[Service[Req, Rep]]
単なるタイプエイリアス
Router[Service[Req, Rep]]
)。
Endpoint
は暗黙的にFinagleサービスに変換できるため、Finagle HTTP APIで
Endpoint
を透過的に使用できます。
val users: Endpoint[HttpRequest, HttpResponse] = (Get / "users" / long /> GetUser) | (Post / "users" /> PostUser) | (Get / "users" /> GetAllUsers) Httpx.serve(":8081", users)
RequestReader
抽象化
io.finch.request.RequestReader
はFinchのキーです。 明らかに、ほとんどのREST API(ビジネスロジックを除く)は、クエリパラメーターの読み取りと検証を行っています。 これが
RequestReader
です。 Finchのすべてのものと同様に、
RequestReader[A]
は
HttpRequest => Future[A]
関数
HttpRequest => Future[A]
。 したがって、
RequestReader[A]
はHTTP要求を受信し、そこからタイプ
A
値を読み取ります
A
主にデータフローサービスの追加の
Future
変換(通常は最初)としてパラメーターを読み取る段階を表すために、結果は
Future
に配置されます。 したがって、
RequestReader
が
RequestReader
返す
Future.exception
、それ以上の変換は実行されません。 この動作は、パラメータの1つが無効な場合にサービスが実際の作業を行うべきではない場合の99%で非常に便利です。
次の例では、
RequestReader
title
は必要なクエリ文字列パラメーター「title」を
NotPresent
か、パラメーターが要求にない場合は
NotPresent
例外を返します。
val title: RequestReader[String] = RequiredParam("title") def hello(name: String) = new Service[HttpRequest, HttpResponse] { def apply(req: HttpRequest) = for { t <- title(req) } yield Ok(s"Hello, $t $name!") }
io.finch.request
パッケージは、HTTPリクエストからさまざまな情報を読み取るための組み込みの
RequestReader
豊富なセットを提供します。クエリ文字列パラメーターからCookieまで。 使用可能なすべての
RequestReader
は、必須(必須)とオプション(オプション)の2つのグループに分けられます。 必須のリーダーは
NotPresent
値または例外を
NotPresent
、オプションのリーダーは
Option[A]
読み取ります。
val firstName: RequestReader[String] = RequiredParam("fname") val secondName: RequestReader[Option[String]] = OptionalParam("sname")
ルートコンビネータと
RequestReader
、
RequestReader
は、2つのリーダーを1つに構成するために使用できるAPIを提供します。 そのような2つのAPIがあります:モナド(
flatMap
を使用)および適用(
~
を使用)。 モナド構文はおなじみに見えますが、適用可能な構文を使用することを強くお勧めします。これにより、エラーを蓄積できますが、モナドのフェイルファーストの性質は最初のエラーのみを返します。 以下の例は、リーダーを作成するための両方の方法を示しています。
case class User(firstName: String, secondName: String) // the monadic style val monadicUser: RequestReader[User] = for { firstName <- RequiredParam("fname") secondName <- OptionalParam("sname") } yield User(firstName, secondName.getOrElse("")) // the applicate style val applicativeUser: RequestReader[User] = RequiredParam("fname") ~ OptionalParam("sname") map { case fname ~ sname => User(fname, sname.getOrElse("")) }
特に、
RequestReader
使用
RequestReader
と、リクエストからString以外の型の値を読み取ることができ
RequestReader
。
RequestReader.as[A]
メソッドを使用して、読み取り値を変換できます。
case class User(name: String, age: Int) val user: RequestReader[User] = RequiredParam("name") ~ OptionalParam("age").as[Int] map { case name ~ age => User(fname, age.getOrElse(100)) }
as[A]
メソッドの魔法は、
DecodeRequest[A]
型の暗黙的なパラメーターに基づいています。 型クラス
DecodeRequest[A]
は、
String
から型
A
を取得する方法に関する情報を保持します。 変換エラーの場合、
RequestReader
は
NotParsed
例外を読み取ります。
Int
、
Long
、
Float
、
Double
および
Boolean
型へのそのままの変換がサポートされています。
RequestReader
のJSONサポートは同じ方法で実装されます
as[Json]
場合、現在のスコープに
DecodeRequest[Json]
実装がある場合、
as[Json]
メソッドを使用できます。 次の例では、
RequestReader
user
は、HTTP要求の本文でJSON形式でシリアル化されたユーザーを読み取ります。
val user: RequestReader[Json] = RequiredBody.as[Json]
Jackson JSONライブラリのサポートを考慮すると、
RequestReader
を使用したJSONオブジェクトの
RequestReader
大幅に簡素化されます。
import io.finch.jackson._ case class User(name: String, age: Int) val user: RequestReader[User] = RequiredBody.as[User]
要求パラメーターは、
RequestReader.should
および
RequestReader.shouldNot
を使用して
RequestReader.should
RequestReader.shouldNot
。 検証には2つの方法があります。インラインルールを使用する方法と、既製の
ValidationRule
を使用する方法です。 以下の例では、
age
リーダーは
age
パラメーターを読み取ります。ただし、0より大きく120より小さい場合は、リーダーは
NotValid
例外を読み取ります。
val age: RequestReader[Int] = RequiredParam("age").as[Int] should("be > than 0") { _ > 0 } should("be < than 120") { _ < 120 }
io.finch.request
パッケージの既製のルール
and
ValidationRule
or
/
and
composersを使用して、上記の例をより簡潔なスタイルに書き換えることができます。
val age: RequestReader[Int] = RequiredParam("age").as[Int] should (beGreaterThan(0) and beLessThan(120))
ResponseBuilder
io.finch.response
パッケージは、HTTP応答を構築するためのシンプルなAPIを提供します。
Ok
または
Created
など、応答ステータスコードに対応する特定の
ResponseBuilder
を使用するのが一般的な方法です。
val response: HttpResponse = Created("User 1 has been created") // plain/text response
io.finch.response
パッケージの重要な抽象化は、タイプクラス
EncodeResponse[A]
です。
ResponseBuilder
は、現在のスコープに暗黙的な値
EncodeResponse[A]
がある場合、任意のタイプ
A
からHTTP応答を構築できます。 したがって、JSONサポートは
ResponseBuilder
で実装されます。サポートされる各ライブラリには、
EncodeResponse[A]
実装があります。 次のコードは、
finch-json
モジュールの標準JSON実装との統合を示しています。
import io.finch.json._ val response = Ok(Json.obj("name" -> "John", "id" -> 0)) // application/json response
したがって、値
EncodeResponse[A]
を目的のタイプの現在のスコープに追加すること
ResponseBuilder
、
ResponseBuilder
の機能を拡張できます。 たとえば、タイプが
User
。
case class User(id: Int, name: String) implicit val encodeUser = EncodeResponse[User]("applciation/json") { u => s"{\"name\" : ${u.name}, \"id\" : ${u.id}}" } val response = Ok(User(10, "Bob")) // application/json response
おわりに
フィンチは非常に若いプロジェクトであり、「重大な欠陥」のない「銀の弾丸」ではありません。 これは、一部の開発者が作業中のタスクに効果的であると感じるツールにすぎません。 この出版物が、プロジェクトでフィンチを使用/試してみることにしたロシア語を話すプログラマーの出発点になることを願っています。