Spring 5のFunctional Web Frameworkの概要
1. 前書き
Spring WebFluxフレームワークは、リアクティブな原則を使用して構築された新しい機能的なWebフレームワークを導入します。
このチュートリアルでは、このフレームワークを実際に使用する方法を学習します。
これは、既存のチュートリアルGuide to Spring 5 WebFluxに基づいています。 そのガイドでは、注釈ベースのコンポーネントを使用して、小さなリアクティブRESTアプリケーションを作成しました。 ここでは、代わりに機能フレームワークを使用します。
2. メーベン依存
前の記事で定義したものと同じ依存関係spring-boot-starter-webfluxが必要です。
org.springframework.boot
spring-boot-starter-webflux
2.0.3.RELEASE
3. 機能的なWebフレームワーク
機能的なWebフレームワークは、リクエストをルーティングおよび処理するために関数を使用する新しいプログラミングモデルを導入します。
アノテーションマッピングを使用するアノテーションベースのモデルとは対照的に、ここではHandlerFunctionとRouterFunctionsを使用します。
また、機能的なWebフレームワークは、注釈ベースのリアクティブフレームワークが構築された同じリアクティブスタック上に構築されます。
3.1. HandlerFunction
HandlerFunctionは、ルーティングされた要求に対する応答を生成する関数を表します。
@FunctionalInterface
public interface HandlerFunction {
Mono handle(ServerRequest request);
}
このインターフェースは主にFunction<Request, Response<T>>であり、サーブレットのように動作します。
ただし、標準のサーブレットServlet.service(ServletRequest req, ServletResponse res)と比較すると、HandlerFunctionは応答をパラメーターとして受け取るのではなく返します。これにより、副作用がなくなり、テストと再利用が容易になります。
3.2. RouterFunction
RouterFunctionは、@RequestMappingアノテーションの代替として機能します。 着信リクエストをハンドラー関数にルーティングするために使用されます。
@FunctionalInterface
public interface RouterFunction {
Mono> route(ServerRequest request);
// ...
}
通常、完全なルーター関数を作成する代わりに、RouterFunctions.route(),ヘルパー関数をインポートしてルートを作成できます。
これにより、RequestPredicate.を適用して要求をルーティングできます。述語が一致すると、2番目の引数であるハンドラー関数が返されます。
public static RouterFunction route(
RequestPredicate predicate,
HandlerFunction handlerFunction)
RouterFunctionを返すことにより、route()をチェーンおよびネストして、強力で複雑なルーティングスキームを構築できます。
4. Functional Webを使用したリアクティブRESTアプリケーション
guide to Spring WebFlux tutorialでは、注釈付きの@RestController とWebClient. を使用して小さなEmployeeManagement RESTアプリケーションを作成します
それでは、Router関数とHandler関数を使用して同じアプリケーションを作成しましょう。
まず、we’ll create routes using RouterFunction to publish and consume our reactive streams of Employee. RoutesはSpring Beanとして登録され、Spring構成として使用される任意のクラス内に作成できます。
4.1. 単一のリソース
単一のEmployeeリソースを公開するRouterFunction を使用して最初のルートを作成しましょう。
@Bean
RouterFunction getEmployeeByIdRoute() {
return route(GET("/employees/{id}"),
req -> ok().body(
employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class));
}
それを分解するために、最初の引数は、handler.を呼び出すHTTPGETリクエストを定義します。 パスが/employee/{id} で、リクエストのタイプがGETの場合にのみ、一致したEmployeeをemployeeRepository から返します。
4.2. 収集リソース
次に、コレクションリソースを公開するために、別のルートを追加しましょう。
@Bean
RouterFunction getAllEmployeesRoute() {
return route(GET("/employees"),
req -> ok().body(
employeeRepository().findAllEmployees(), Employee.class));
}
4.3. 単一リソースの更新
最後に、Employee resourceを更新するためのルートを追加しましょう。
@Bean
RouterFunction updateEmployeeRoute() {
return route(POST("/employees/update"),
req -> req.body(toMono(Employee.class))
.doOnNext(employeeRepository()::updateEmployee)
.then(ok().build()));
}
5. ルートを作成する
1つのルーター機能でルートをまとめることもできます。
上で作成したルートを組み合わせてみましょう。
@Bean
RouterFunction composedRoutes() {
return
route(GET("/employees"),
req -> ok().body(
employeeRepository().findAllEmployees(), Employee.class))
.and(route(GET("/employees/{id}"),
req -> ok().body(
employeeRepository().findEmployeeById(req.pathVariable("id")), Employee.class)))
.and(route(POST("/employees/update"),
req -> req.body(toMono(Employee.class))
.doOnNext(employeeRepository()::updateEmployee)
.then(ok().build())));
}
ここでは、RouterFunction.and() を使用してルートを結合しています。
最後に、EmployeeManagement アプリケーションに必要なRouterとHandler を使用してすべてのRESTAPIを作成しました。 アプリケーションを実行するには、異なるルートを使用するか、上記で作成した単一のルートを使用します。
6. ルートのテスト
WebTestClientを使用してルートをテストできます。
WebTestClient を使用してルートをテストするには、bindToRouterFunction を使用してルートをバインドし、テストクライアントインスタンスを構築する必要があります。
getEmployeeByIdRouteをテストしてみましょう。
@Test
public void givenEmployeeId_whenGetEmployeeById_thenCorrectEmployee() {
WebTestClient client = WebTestClient
.bindToRouterFunction(config.getEmployeeByIdRoute())
.build();
Employee expected = new Employee("1", "Employee 1");
given(employeeRepository.findEmployeeById("1")).willReturn(Mono.just(employee));
client.get()
.uri("/employees/1")
.exchange()
.expectStatus()
.isOk()
.expectBody(Employee.class)
.isEqualTo(expected);
}
同様に、getAllEmployeesRoute:
@Test
public void whenGetAllEmployees_thenCorrectEmployees() {
WebTestClient client = WebTestClient
.bindToRouterFunction(config.getAllEmployeesRoute())
.build();
List employeeList = new ArrayList<>();
Employee employee1 = new Employee("1", "Employee 1");
Employee employee2 = new Employee("2", "Employee 2");
employeeList.add(employee1);
employeeList.add(employee2);
Flux employeeFlux = Flux.fromIterable(employeeList);
given(employeeRepository.findAllEmployees()).willReturn(employeeFlux);
client.get()
.uri("/employees")
.exchange()
.expectStatus()
.isOk()
.expectBodyList(Employee.class)
.isEqualTo(employeeList);
}
更新されたEmployee インスタンスがEmployeeRepositoryを介して更新されることを表明することにより、updateEmployeeRouteをテストすることもできます。
@Test
public void whenUpdateEmployee_thenEmployeeUpdated() {
WebTestClient client = WebTestClient
.bindToRouterFunction(config.updateEmployeeRoute())
.build();
Employee employee = new Employee("1", "Employee 1 Updated");
client.post()
.uri("/employees/update")
.body(Mono.just(employee), Employee.class)
.exchange()
.expectStatus()
.isOk();
verify(employeeRepository).updateEmployee(employee);
}
WebTestClientを使用したテストの詳細については、working with WebClient and WebTestClientに関するチュートリアルを参照してください。
7. 概要
このチュートリアルでは、Spring 5で新しい機能的なウェブフレームワークを紹介し、その2つのコア関数Router とHandler を調べ、リクエストを処理してレスポンスを送信するためのさまざまなルートを作成する方法を学びました。
また、Guide to Spring 5 WebFluxで導入されたEmployeeManagement アプリケーションを新しいフレームワークで再作成しました。
Reactorに基盤を置くと、リアクティブフレームワークは、データストアへのリアクティブアクセスで完全に輝きます。 残念ながら、MongoDBなどのいくつかのNoSQLデータベースを除いて、ほとんどのデータストアはまだそのようなリアクティブアクセスを提供していません。
いつものように、完全なソースコードはover on Githubで見つけることができます。