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(""))
特に、
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")
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))
したがって、値
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"))
おわりに
フィンチは非常に若いプロジェクトであり、「重大な欠陥」のない「銀の弾丸」ではありません。 これは、一部の開発者が作業中のタスクに効果的であると感じるツールにすぎません。 この出版物が、プロジェクトでフィンチを使用/試してみることにしたロシア語を話すプログラマーの出発点になることを願っています。