Finagle / Finchの純粋に機能的なREST API

フィンチ


Finchライブラリの歴史は約1年前にConfettinの地下で始まり、そこでFinagleで REST APIを作成しようとしました 。 finagle-http自体は非常に優れたツールであるという事実にもかかわらず、より豊かな抽象化の深刻な不足を感じ始めました。 さらに、これらの抽象化には特別な要件がありました。 それらは不変で、簡単に構成可能で、同時に非常に単純であると想定されていました。 関数としてシンプル。 そこで、Finchライブラリが登場しました。これは、finagle-httpの上にある非常に薄い関数と型の層であり、finagle-httpでのHTTP(マイクロ|ナノ)サービスの開発をより楽しく簡単にします。

6か月前ライブラリの最初の安定バージョンがリリースされ、先日、バージョン0.5.0がリリースされました 。 この間、 6社 (うち3 はまだ公式リストにありません: MesosphereShponicGlobo.com )がFinchの生産を開始し、そのうちのいくつかは積極的な貢献者にさえなりました。

この投稿では、Finchが構築する3つの柱、 RouterRequestReaderおよびResponseBuilderます。

Router


io.finch.routeパッケージは、ルートコンビio.finch.route APIを実装します。これにより、ボックスから利用可能なプリミティブルーターからルーターを組み合わせて、無限の数のルーターを構築できます。 Parser Combinatorsscodecは同じアプローチを使用します。

ある意味では、 Router[A]は機能Route => Option[(Route, A)]です。 Routerは抽象ルートRouteを取り、残りのルートと取得したタイプA値からOptionを返しますA つまり、 Routerは、成功した場合(要求をルーティングできる場合Some(...)Some(...)返します。

基本的なルーターは、 intlongstring 、およびboolean 4つだけです。 さらに、ルートから値を抽出せず、単純にサンプルにマッピングするルーターがあります(たとえば、HTTPメソッドのルーター: GetPost )。

次の例は、ルーターを構成するための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に配置されます。 したがって、 RequestReaderRequestReader返す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") 

ルートコンビネータとRequestReaderRequestReaderは、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以外の型の値を読み取ることができRequestReaderRequestReader.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を取得する方法に関する情報を保持します。 変換エラーの場合、 RequestReaderNotParsed例外を読み取ります。 IntLongFloatDoubleおよび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]を目的のタイプの現在のスコープに追加することResponseBuilderResponseBuilderの機能を拡張できます。 たとえば、タイプが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 

おわりに


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

Source: https://habr.com/ru/post/J250591/


All Articles