この記事では、 機能的なWebフレームワークと呼ばれるSpring Framework 5の新しいコンセプトについて説明し、軽量のアプリケーションとマイクロサービスの開発にどのように役立つかを説明します。
Springとマイクロフレームワークが1つの文になっていることに驚くかもしれません。 しかし、そうです、 Springはあなたの次のJavaマイクロフレームワークかもしれません。 混乱を避けるために、 microの意味を定義してみましょう。
- Laconic-最小の定型句、最小の設定
- シンプル-マジックなし
- 展開が簡単-展開用の1つのアーティファクト
- 実行が簡単-追加の依存関係なし
- 軽量-最小限のメモリ/ CPU使用率
- ノンブロッキング-競合するノンブロッキングアプリケーションを作成するため
これらの点のいくつかは、 Spring Bootを使用する場合に関連しますが、 Spring Framework自体に追加の魔法を追加します 。 @Controller
ような基本的なアノテーションでさえ、自動設定やコンポーネントスキャンはもちろん、簡単ではありません。 一般に、大規模なアプリケーションの場合、 SpringがDI、ルーティング、構成などを処理することは、かけがえのないものです。 ただし、アプリケーションが1つの大きなマシンの単なるギアであるマイクロサービスの世界では、 Spring Bootのすべてのパワーが少し冗長になる可能性があります。
この問題を解決するために、Springチームは機能的なWebフレームワークと呼ばれる新しい機能を導入しました。これについて説明します。 全体として、これは以前はSpring Reactive Webと呼ばれていた、より大きなSpring WebFluxサブプロジェクトの一部です。
はじめに、基本に戻って、Webアプリケーションとは何か、その中にどのコンポーネントが含まれているのかを見てみましょう。 もちろん、基本的なものがあります-Webサーバーです。 リクエストの手動処理とアプリケーションメソッドの呼び出しを回避するには、 ルーターが役立ちます 。 最後に、 ハンドラーが必要です。これは、リクエストを受け入れて答えを返すコードです。 実際、必要なのはそれだけです! そして、 機能的なSpring Webフレームワークが提供するのはこれらのコンポーネントであり、すべての魔法を取り除き、基本的な最小限に焦点を合わせます。 これは、 Springが急激に方向を変えてSpring MVCから離れることを意味するものではないことに注意してください。
ハンドラー
例を見てみましょう。 はじめに、 Spring Initializrに移動し、 Spring Boot 2.0とReactive Webを唯一の依存関係として使用して新しいプロジェクトを作成しましょう。 これで、最初のハンドラー 、つまりリクエストを受信して応答を返す関数を作成できます。
HandlerFunction hello = new HandlerFunction() { @Override public Mono handle(ServerRequest request) { return ServerResponse.ok().body(fromObject("Hello")); } };
したがって、 ハンドラーは、( ServerRequest
型の) request
パラメーターを受け取り、テキスト "Hello"を持つServerResponse
型のオブジェクトを返すHandlerFunction
インターフェースの単なる実装です。 Springは、サーバーからの応答を作成する便利なビルダーも提供します。 この場合、 ok()
を使用して、200のHTTP応答コードを自動的に返します。応答を返すには、提供されたオブジェクトから応答を作成するためのもう1つのヘルパーfromObject
が必要です。
また、コードをもう少し簡潔にし、Java 8などのラムダを使用することもできます。 HandlerFunction
は単一の抽象メソッドインターフェイス(SAM)であり、次のように関数を記述できます。
HandlerFunction hello = request -> ServerResponse.ok().body(fromObject("Hello"));
ルーター
ハンドラーができたので、今度はrouterを定義します 。 たとえば、HTTP GET
メソッドを使用してURL "/"が呼び出されたときにハンドラーを呼び出します。 これを実現するには、ハンドラー関数をルートにマップするタイプRouterFunction
オブジェクトを定義します。
RouterFunction router = route(GET("/"), hello);
route
とGET
はRouterFunctions
とRouterFunctions
静的メソッドであり、いわゆるRouterFunction
を作成できます。 このような関数はリクエストを受け取り、すべての述語(URL、メソッド、コンテンツタイプなど)に一致するかどうかを確認し、目的のハンドラー関数を呼び出します。 この場合、述部はhttp GETメソッドおよびURL '/'であり、ハンドラー関数はhello
であり、これは上で定義されています。
Webサーバー
そして今、すべてを1つのアプリケーションにまとめるときです。 軽量でシンプルなReactive Netty
サーバーを使用します。 ルーターをWebサーバーと統合するには、それをHttpHandler
に変換する必要があります。 その後、サーバーを起動できます。
HttpServer .create("localhost", 8080) .newHandler(new ReactorHttpHandlerAdapter(httpHandler)) .block();
ReactorHttpHandlerAdapter
はNettyが提供するクラスで、 HttpHandler
を受け入れます。残りのコードについては説明は不要だと思います。 localhost
ホストとポート8080
バインドされた新しいWebサーバーを作成し、ルーターから作成されたhttpHandler
を提供します。
これで、アプリケーションの準備ができました! そしてその完全なコード:
public static void main(String[] args) throws IOException, LifecycleException, InterruptedException { HandlerFunction hello = request -> ServerResponse.ok().body(fromObject("Hello")); RouterFunction router = route(GET("/"), hello); HttpHandler httpHandler = RouterFunctions.toHttpHandler(router); HttpServer .create("localhost", 8080) .newHandler(new ReactorHttpHandlerAdapter(httpHandler)) .block(); Thread.currentThread().join(); }
最後の行は、JVMプロセスを存続させるためにのみ必要です。 HttpServer自体はブロックしません。 すぐにアプリケーションが起動することに気付くかもしれません-コンポーネントのスキャンや自動設定はありません。
このアプリケーションを通常のJavaアプリケーションとして実行することもできます;アプリケーションコンテナーは必要ありません。
デプロイメントアプリケーションをパッケージ化するには、 Maven Springプラグインを利用して 、単に
./mvnw package
このコマンドは、JARに含まれるすべての依存関係を使用して、いわゆるファットJARを作成します。 このファイルは、JRE以外がインストールされていなくてもデプロイおよび実行できます。
java -jar target/functional-web-0.0.1-SNAPSHOT.jar
また、アプリケーションのメモリ使用量を確認すると、32 MBの領域にとどまり、メタスペース(クラス)で22 MBが使用され、ヒープで約10 MBが直接使用されていることがわかります。 もちろん、私たちのアプリケーションは何もしませんが、それでもフレームワークとランタイムだけで最小限のシステムリソースが必要なことを示しています。
JSONサポート
この例では、文字列を返しましたが、JSON応答を返すのも同じくらい簡単です。 JSONを返す新しいエンドポイントでアプリケーションを拡張しましょう。 私たちのモデルは非常に単純です-nameというname
1つの文字列フィールドです。 不要な定型コードを避けるために、 Lombokプロジェクトの機能である@Data
アノテーションを使用します。 このアノテーションを使用すると、ゲッター、セッター、 equals
、およびhashCode
メソッドが自動的に作成されるため、これらを手動で解放する必要はありません。
@Data class Hello { private final String name; }
ここで、URL /json
にアクセスするときにJSON応答を返すようにルーターを拡張する必要があります。 これは、既存のルートでandRoute(...)
メソッドを呼び出すことで実行できます。 また、ルーターコードを別の関数に入れて、アプリケーションコードから分離し、後でテストで使用できるようにします。
static RouterFunction getRouter() { HandlerFunction hello = request -> ok().body(fromObject("Hello")); return route( GET("/"), hello) .andRoute( GET("/json"), req -> ok() .contentType(APPLICATION_JSON) .body(fromObject(new Hello("world"))); }
再起動後、アプリケーションは、タイプapplication/json
コンテンツを要求するときにURL /json
にアクセスすると{ "name": "world" }
を返します。
アプリケーションコンテキスト
お気づきかもしれませんが、アプリケーションコンテキストを定義しなかったため、単に必要ではありません! RouterFunction
をSpring WebFluxアプリケーションのコンテキストでBeanとして宣言でき、特定のURLのリクエストも処理できるという事実にもかかわらず、ルーターはNettyサーバーの上で簡単に起動して、シンプルで軽量なJSONサービスを作成できます。
テスト中
リアクティブアプリケーションをテストするために、 SpringはWebTestClient
と呼ばれる新しいクライアントを提供します( WebTestClient
似ていMockMvc
)。 既存のアプリケーションコンテキスト用に作成できますが、 RouterFunction
用に定義することもできます。
public class FunctionalWebApplicationTests { private final WebTestClient webTestClient = WebTestClient .bindToRouterFunction( FunctionalWebApplication.getRouter()) .build(); @Test public void indexPage_WhenRequested_SaysHello() { webTestClient.get().uri("/").exchange() .expectStatus().is2xxSuccessful() .expectBody(String.class) .isEqualTo("Hello"); } @Test public void jsonPage_WhenRequested_SaysHello() { webTestClient.get().uri("/json").exchange() .expectStatus().is2xxSuccessful() .expectHeader().contentType(APPLICATION_JSON) .expectBody(Hello.class) .isEqualTo(new Hello("world")); } }
WebTestClient
は、HTTPコード、応答の内容、応答の種類などを検証するために、受信した応答に適用できる多くのWebTestClient
が含まれています。
結論として
Spring 5では、小型で軽量のマイクロサービススタイルのWebアプリケーションを開発するための新しいパラダイムを導入しています。 このようなアプリケーションは、アプリケーションコンテキスト、自動構成なしで動作し、一般にルーターおよびハンドラー機能とWebサーバーがアプリケーション本体で明示的に定義されている場合、 マイクロフレームワークアプローチを使用します。
コード
GitHubで利用可能
参照資料
- Spring 5の新機能:機能的Webフレームワーク
- リアクティブプログラミングに関するメモパートII:コードの記述
- BaeldungのSpring Functional Webの非常に素晴らしいイントロ-Spring 5のFunctional Web Frameworkの紹介
- Spring 5.0 M1によるリアクティブプログラミング
- Redditの元の記事の議論
翻訳者から
私は元の記事の著者でもあるため、コメントで質問することができます。