背景・目的
過去に、Spring Bootについて下記の記事にまとめました。今回は、Spring WebFluxを試してみます。
- Spring BootでDAOからデータベースにアクセスしてみた-その2(クライテリア)
- Spring BootでDAOからデータベースにアクセスしてみた-その1(DAOとアノテーション)
- Spring Bootでバリデーションを試してみた
- Spring Bootでデータを保存する-その2
- Spring Bootでデータを保存する-その1
- Spring Bootでデータベースへのアクセスを試してみた
- Sprint Bootの環境を構築したときのメモ
まとめ
下記に特徴をまとめます。
特徴 | 説明 |
---|---|
Spring WebFlux | ・完全なノンブロッキング ・Reactive Streamsバックプレッシャーをサポート ・Netty、Undertow、サーブレットコンテナなどのサーバ上で実行される |
リアクティブ | データストリームと変更の伝播に関係する宣言型 プログラミングの一種 |
ノンブロッキング | ・プログラミング処理のこと ・処理の進捗を確認しながら同時に並行して複数の処理が可能 ・入力に対して、待ち時間がないのでスループットが向上する |
適用範囲 | Spring MVC、WebFluxのどちらを利用するかということ。 ただし、相互の継続性と一貫性を考慮して設計されており、並行して使用できる |
使用できるサーバ | サーブレットコンテナ ・Tocat ・Jetty 非サーブレットコンテナ ・Netty ・Undertow |
パフォーマンス | リアクティブ&ノンブロッキングでは、アプリケーションの実行速度は向上しない WebClientを使って、リモート呼び出しを並列で実行すると、場合によっては高速化する ただし、ノンブロッキング方式で処理にするには、より多くの作業が必要になり、必要な処理時間はわずかに長くなる メリットは、少数の固定スレッドと少ないメモリで拡張できる点 |
スレッドモデル | ・サーバ用に1つのスレッド ・リクエスト用に複数のスレッド(通常はCPUコアと同数) これに対して、サーブレットコンテナでは、サーブレットI/O(ブロッキング)とサーブレット3.1(非ブロッキング)I/Oの両方をサポートするので、より多くのスレッドで開始される |
設定 | Spring Framework は、 サーバーの起動と停止をサポートしていない |
概要
Spring WebFluxを基に整理します。
- Spring WebFluxは、Springバージョン5.0から追加された
- 完全なノンブロッキング
- Reactive Streamsバックプレッシャーをサポート
- Netty、Undertow、サーブレットコンテナなどのサーバ上で実行される
- spring-webmvcとspring-webfluxは、Spring Framework内で共存する
Overview
背景
作成された背景について、下記に記載いたします。
- 少数のスレッドで並行処理し、少ないHWリソースで拡張できるノンブロッキングWebスタックが必要だった
- サーブレットAPIの場合、下記の特徴がある
- 同期的なFilter、Servlet
- ブロッキングなgetParameter、getPart
- サーブレットAPIの場合、下記の特徴がある
- 関数型プログラミング
- アノテーション(Java5)や、ラムダ式(Java8)などにより、非同期ロジックの宣言的構成を可能にする非ブロッキングアプリと継続APIによって恩恵がある
リアクティブとは
Reactive programmingを基に整理します。
- データストリームと変更の伝播に関係する宣言型 プログラミング
- リアクティブプログラミングは、インタラクティブなユーザーインターフェイスとほぼリアルタイムのシステムアニメーションの作成を簡素化する方法として提案されている
- リアクティブ プログラミングは、さまざまなモデルとセマンティクスによって制御される。これらは、次のとおり分類できる
- 同期: 時間の同期モデルと非同期モデル
- 決定論: 決定論的評価プロセスと非決定論的評価プロセスおよび結果
- 更新プロセス:コールバックとデータフローとアクター
https://docs.spring.io/spring-framework/reference/web/webflux/new-framework.html
Spring WebFlux>Overviewでは、下記の記載もあります。
- ブロックされるのではなく、操作が完了したりデータが利用可能になったりしたときに通知に反応する
ノンブロッキング
- ノンブロッキングはプログラミング処理のこと
- 処理の進捗を確認しながら同時に並行して複数の処理が可能
- 入力に対して、待ち時間がないのでスループットが向上する
- ブロッキングとノンブロッキングの比較は下記の通り
ブロッキング | ノンブロッキング | |
---|---|---|
処理の順序 | 逐次的に順番に処理 | 並列処理 |
待ち時間 | 処理の間で待ち時間が発生する | 待ち時間は発生しない |
Applicability
- Spring MVC or WebFlux?
- 相互の継続性と一貫性を考慮して設計されており、並行して使用できる
考慮点は、下記の通り
- すでにMVCアプリケーションが存在し問題ない場合は、そのままでよい
- ほとんどのライブラリは、ブロッキングが前提であり、ライブラリの選択肢は豊富にある
- ノンブロッキングを探している場合、Spring WebFluxは、候補になり得る
- サーバ
- Netty
- Tomcat
- Jetty
- Undertow
- Servlet
- プログラミングモデル
- annotated controllers and functional web endpoints
- アクティブライブラリ
- Reactor
- RxJava
- サーバ
- Java 8 ラムダ、Kotlinで使用する軽量かつ、機能的なWebフレームワークに関心がある場合、Spring WebFlux機能Webエンドポイントを使用できる
- 透明性、制御性の向上によるメリットが得られる
- 要件が複雑ではない小規模アプリやマイクロサービスにも適している
- Spring MVC、Spring WebFluxコントローラー、Spring WebFlux機能エンドポイントのいずれかを使用したアプリを混在できる
- blocking persistence APIs、ネットワークAPIを使用する場合は、一般的なアーキテクチャでは、Spring MVCが最適
- ReactorとRxJavaの両方でブロッキング呼び出しを別スレッドで実行することは可能だが、ノンブロッキングのメリットを最大限活かせない
- リモートサービスへの呼び出しを含むSpring MVCがある場合、reactive Web Clientを試してみる
- Spring MVCコントローラーのメソッドから、リアクティブ型(Reactor、RxJava等)を直接返せる
- レイテンシーが大きい、相互依存性が大きいほどメリットは大きい
- 大規模なチームは、ノンブロッキング、関数型、宣言型プログラミングへの移行における学習曲線を意識すること
- 小規模から始める。大規模に行わない。メリットを測定する
Server
- Spring WebFluxでは、下記をサポートしている
- サーブレットコンテナ
- Tocat
- Jetty
- 非サーブレットコンテナ
- Netty
- Undertow
- サーブレットコンテナ
- Spring Bootでは、WebFluxスターターがある
- スターターでは、Nettyを使用するが、Maven等を変更しTomcat等に切り替えることも可能
- TomcatとJettyは、Spring MVCとWebFluxの両方で使用できる
- Spring MVCはサーブレットブロッキングI/Oに依存する
- Spring WebFluxはサーブレットノンブロッキングに依存する
- 低レベルアダプターの背後で、サーブレットAPIを使用する
- Undertowでは、Spring WebFluxはサーブレットAPIを使用せずに、Undetow APIを直接使用する
パフォーマンス
- リアクティブ&ノンブロッキングでは、アプリケーションの実行速度は向上しない
- WebClientを使って、リモート呼び出しを並列で実行すると、場合によっては高速化する
- ただし、ノンブロッキング方式で処理にするには、より多くの作業が必要になり、必要な処理時間はわずかに長くなる
- メリットは、少数の固定スレッドと少ないメモリで拡張できる点
Concurrency Model
- 並行性モデルと、ブロッキングとスレッドのデフォルトの想定には重要な違いがある
- Spring MVC
- アプリケーションが、現在のスレッドをブロックできる事が前提
- サーブレットコンテナは、リクエスト処理中に発生する可能性があるブロッキングを吸収するため、大きなスレッドプールを使用する
- Spring WebFlux
- アプリケーションが、ブロッキングされないことが前提
- ノンブロッキングサーバでは、小さな固定サイズのスレッドプールを使用してリクエストを処理する
ブロッキングAPIの呼び出し
- ブロッキングライブラリを使用するには、ReactorとRxJavaの両方で、別スレッド処理を続行するためのpublishOn演算子を用意している
Mutable State
- Reactor と RxJava では、演算子を使用してロジックを宣言する
- 実行時に、データが個別の段階で順番に処理されるリアクティブ パイプラインが形成される
- メリットは、パイプライン内のアプリケーション コードが、同時に呼び出されることがないため、アプリケーションが可変状態を保護する必要がなくなる
スレッドモデル
- Vanilla Spring WebFluxサーバでは、下記を想定している
- サーバ用に1つのスレッド
- リクエスト用に複数のスレッド(通常はCPUコアと同数)
- サーブレットコンテナは、サーブレットI/O(ブロッキング)とサーブレット3.1(非ブロッキング)I/Oの両方をサポートするので、より多くのスレッドで開始される
- リアクティブ WebClientは、イベントループスタイルで動作する
- それに関連する少数の固定された処理スレッドが表示される
- Reactor Nettyがクライアントとサーバの両方に使用される場合、2つはデフォルトでイベントループリソースを共有する
- ReactorとRxJavaは、処理を別のスレッド プールに、切り替えるために使用されるpublishOn演算子で使用するスケジューラーと呼ばれるスレッドプールの抽象化を提供する
- 同時実行戦略の名前がつけられる
- 例えば、parallel、elastic
- 同時実行戦略の名前がつけられる
- データアクセスライブラリやその他のサードパーティの依存関係も、独自のスレッドを作成して使用できる
設定
- Spring Framework は、 サーバーの起動と停止をサポートしていない
- サーバーのスレッド モデルを構成するには、サーバー固有の構成 API を使用するか、Spring Boot を使用する場合は、各サーバーの Spring Boot 構成オプションを確認する必要がある
実践
前提
- VSCodeを起動します
- コマンドパレッドを開きます
- Spring Spring Initializrを選択します
- 3.3.1を選択します
- Javaを選択します
- GroupIdとArtifactIdを設定します
- packagetypeをJarとします
- Javaのバージョンを17とします
- Spring Reactive Webを選択します
- 追加されました
RestControllerの作成
とりあえず、最小構成で試してみます
リクエストハンドラ
- SampleRestController.javaを、SpringBootアプリケーションと同じパッケージに追加します
- 下記のコードを追加します
package com.example.webflux.sample; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SampleRestController { @RequestMapping("/") public String index() { return "Hello, World!"; } }
動作確認
- ビルドと起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080を開きます。表示されました
ノンブロッキング処理
WebFluxでは、処理をラップし、ノンブロッキングにする機能が用意されています。
「Flux」、「Mono」というノンブロッキングラッパーの機能があります。
それぞれは、下記の違いあります。
- Flux
- 複数オブジェクトに対応
- Mono
- 単一オブジェクに対応
Mono
当該クラスは、単一オブジェクトを扱います。
リクエストハンドラ
- 下記のコードを追加します
- fluxメソッドを追加しています
package com.example.webflux.sample; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; // Add this import statement @RestController public class SampleRestController { @RequestMapping("/") public String index() { return "Hello, World!"; } @RequestMapping("/flux") public Mono<String> flux() { return Mono.just("Hello, Flux(Mono)."); } }
- Mono.just
- Monoはジェネリックスをサポート
- 引数に指定したオブジェクトをラップしたMonoインスタンスが生成される
確認
- ビルドと起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080を開きます。表示されました
Fluxクラス
当該クラスは、複数オブジェクトを扱います。
リクエストハンドラ
- 下記のコードを追加します
- flux2メソッドを追加しています
package com.example.webflux.sample; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Mono; // Add this import statement import reactor.core.publisher.Flux; // Add this import statement @RestController public class SampleRestController { @RequestMapping("/") public String index() { return "Hello, World!"; } @RequestMapping("/flux") public Mono<String> flux() { return Mono.just("Hello, Flux(Mono)."); } @RequestMapping("/flux2") public Flux<String> flux2() { return Flux.just("Hello, Flux(Flux).","こんにちは、Fluxクラスです"); } }
確認
- ビルドと起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080を開きます。2つまとめて表示されました
DBの利用
Spring Data JPAと、H2を使用してデータベースアクセスし、Mono/Fluxで利用する。
ダミーデーターを利用する
pomの修正
- JPAとH2をインストールします
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency>
エンティティの作成
- ハンドラと同じ場所に、「Post.java」というファイルを作成します
- 下記のコードを追加します
package com.example.webflux.sample; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; @Entity @Table(name = "post") public class Post { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column public int id; @Column public int userId; @Column(nullable = false) public String title; @Column(nullable = false) public String body; public Post() { super(); } public Post(int id, int userId, String title, String body){ super(); this.id = id; this.userId = userId; this.title = title; this.body = body; } public String toString(){ return "{id:"+id+",userId:"+userId+",title:"+title+",body:"+body+"}"; } }
リポジトリの作成
- 上記のエンティティと同じ場所に「PostRepository.java」インタフェイスを配置します
- 下記のコードを追加します
package com.example.webflux.sample; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface PostRepository extends JpaRepository<Post, Integer> { public Post findById(int id); }
- IDでレコードを取得するfindByIdメソッドを用意
リクエストハンドラを修正
- 作成したリクエストハンドラに、下記のコードを追加します
- PostRepositoryのインスタンス変数
@Autowired PostRepository repository;
- postメソッド
@RequestMapping("/post") public Mono<Post> post(){ Post post = new Post(0,0,"title-a","body-a"); return Mono.just(post); }
確認
- ビルドと起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/postを開きます。ダミーで登録した内容が見えました
DBにアクセスする
ハンドラの修正
- ハンドラに下記を追加します
- initメソッドで初期データを登録します
@PostConstruct public void init() { Post post1 = new Post(1, 1, "Hello", "Hello Flux!"); Post post2 = new Post(2, 2, "Sample", "This is sample post."); Post post3 = new Post(3, 3, "ハロー", "これはサンプルです"); repository.saveAndFlush(post1); repository.saveAndFlush(post2); repository.saveAndFlush(post3); }
- postメソッド(パラメータつき)を追加します
@RequestMapping("/post/{id}") public Mono<Post> post(@PathVariable int id){ Post post = repository.findById(id); return Mono.just(post); }
確認
-
ビルドと起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
全レコードを表示する
ハンドラを修正する
- ハンドラに、下記のメソッドを追加します
@RequestMapping("/posts") public Flux<Post> posts(){ List<Post> posts = repository.findAll(); return Flux.fromArray(posts.toArray(new Post[0])); }
確認
ファイルアクセスとネットワークアクセス
データベース以外にも、ファイルアクセスがある。
Spring Bootでは、アプリに必要なファイル類は、resources
フォルダに用意し、読み込む。
sampleファイルを用意
- resoucesフォルダにsample.txtを配置します
- 下記を追記します
This is sample text file. サンプルテキストファイルです。
リクエストハンドラ
- リクエストハンドラに下記を追加します
@RequestMapping("/file") public Mono<String> file(){ String result =""; try { ClassPathResource classPath = new ClassPathResource("sample.txt"); InputStream stream = classPath.getInputStream(); InputStreamReader streamReader = new InputStreamReader(stream); BufferedReader reader = new BufferedReader(streamReader); String line; while((line = reader.readLine()) != null){ result += line + "\n"; } } catch (IOException e) { result = e.getMessage(); } return Mono.just(result); }
確認
Web ClientでJSONデータを取得する
アプリから、NWアクセスを行う場合、WebClientというクラスを利用します。
ハンドラ
- RestControllerにWebClient利用のためのコードを追加します
- WebClientBuilderは、コンストラクタに引数として渡される
- ドメインを指定する
private final WebClient webClient; public SampleRestController(WebClient.Builder webClientBuilder) { super(); webClient = webClientBuilder.baseUrl("jsonplaceholder.typicode.com").build(); }
- メソッドを追加する
@RequestMapping("/web/{id}") public Mono<Post> web(@PathVariable int id){ return this.webClient.get() .uri("/posts/"+id) .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToMono(Post.class); } @RequestMapping("/web") public Flux<Post> web2(){ return this.webClient.get() .uri("/posts") .accept(MediaType.APPLICATION_JSON) .retrieve() .bodyToFlux(Post.class); }
- url:パス
- accept(MediaType):JSON形式
- retrieve:情報の取得
- bodyToFlux、bodyToMono:Flux、Mono形式で取得する
確認
-
ビルドと起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
コントローラーと関数型ルーティング
ここでは、Controllerを使って、関数によるルーティングを試してみます。
コントローラー
関数でルーティングする場合は、リクエストメソッドを使いません。
Router Functionというものを作成します。
- Router Functionは、ルーティング関数を管理するためのクラス
- Router Functionを作成し、Beanとしてアプリに登録することで、指定したルーティングが機能し、アクセスにより処理が実行される
- コントローラーなどのクラス内に、メソッドを用意する
- SampleController.javaを追加します
- 下記のコードを追加します
package com.example.webflux.sample; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import static org.springframework.web.reactive.function.server.RouterFunctions.route; import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import reactor.core.publisher.Mono; // Add this import import static org.springframework.web.reactive.function.server.ServerResponse.ok; // Add this import @Controller public class SampleController { @Bean public RouterFunction<ServerResponse> routes() { return route(GET("/f/hello"), this::hello); } Mono<ServerResponse> hello(ServerRequest request) { return ok().body(Mono.just("Hello Functional routing world"),String.class); } }
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/f/hello にアクセスします。表示されました
複数のrouteを連結する
複数のルーティングを行うために、RouterFunctionではどのように実装するのか試してみます。
RouterFunctionに必要なルーティングをすべて登録します。
コントローラー
- 下記のようにコードを修正します
- routes関数
- andRouteで関数を登録する
@Bean public RouterFunction<ServerResponse> routes() { return route(GET("/f/hello"), this::hello) .andRoute(GET("/f/hello2"), this::hello2); }
- 新しい関数
Mono<ServerResponse> hello2(ServerRequest request) { return ok().body(Mono.just("関数型ルーティングの世界へようこそ!"),String.class); }
- routes関数
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/f/hello2 にアクセスします。表示されました
テンプレートでWebページをレンダリング
Controllerクラスを利用する場合には、テンプレートエンジンを使うことも可能ですが、Spring Webとは異なります。
pom.xml
テンプレートを使うために、thymeleafをインストールします
- pom.xmlに下記のコードを追加します
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
テンプレート
-
下記のコードを追加します。この時点では動的な値は埋め込んでいません
<!DOCTYPE html> <html> <head> <title>Flux top page</title> <meta http-equiv="Content-type" content="text/html"; charset="UTF-8" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" > </head> <body class="container"> <h1 class="display-4">Flux page</h1> <p class="msg">This is Sample WebFlux page!!</p> </body> </body> </html>
リクエストハンドラ
まずはじめに、リクエストハンドラのパターンを試します。
- コントローラークラスに下記を追加します
@RequestMapping("/f/flux") Mono<Rendering> flux() { return Mono.just(Rendering.view("flux").build()); }
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/f/flux にアクセスします。表示されました
関数ルーティング
次に、関数ルーティングを試します。
- コントローラーを修正します
- ルーティングに追加します
@Bean public RouterFunction<ServerResponse> routes() { return route(GET("/f/hello"), this::hello) .andRoute(GET("/f/hello2"), this::hello2) .andRoute(GET("/f/flux2"), this::flux2); }
- メソッドを追加します
Mono<ServerResponse> flux2(ServerRequest request) { return ok().contentType(MediaType.TEXT_HTML).render("flux"); }
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/f/flux2 にアクセスします。表示されました
値をテンプレートに渡す
コントローラーから値をテンプレートに渡します
- flux.htmlを下記のようにタイトル、メッセージを埋め込むように修正します
<!DOCTYPE html> <html> <head> <title>Flux top page</title> <meta http-equiv="Content-type" content="text/html"; charset="UTF-8" /> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" > </head> <body class="container"> <h1 class="display-4" th:text="${title}"></h1> <p class="msg" th:text="${message}"></p> </body> </body> </html>
リクエストハンドラを修正
Rendering.BuilderのmodelAttributeメソッドで値を設定します
- fluxメソッドを下記のように書き換えます
@RequestMapping("/f/flux") Mono<Rendering> flux() { // return Mono.just(Rendering.view("flux").build()); return Mono.just(Rendering.view("flux") .modelAttribute("title","Flux/Request Handler") .modelAttribute("message","これはリクエストハンドラのサンプルです") .build()); }
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/f/flux にアクセスします。表示されました
Model引数を利用する
リクエストハンドラの場合、引数にModelを用意して値を追加もできる
- fluxメソッドを下記のように書き換えます
@RequestMapping("/f/flux") Mono<Rendering> flux(Model model) { model.addAttribute("title","Flux/Request Handler(Using Model Parameter)"); model.addAttribute("message","これはリクエストハンドラのサンプルです"); return Mono.just(Rendering.view("flux").build()); }
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/f/flux にアクセスします。表示されました
関数ルーティングで実装する
renderメソッドの第二引数にパラメータを渡します。
- flux2メソッドを下記のように修正します
Mono<ServerResponse> flux2(ServerRequest request) { // return ok().contentType(MediaType.TEXT_HTML).render("flux"); Map map = new HashMap(); map.put("title","Flux/Functional Routing"); map.put("message","これは関数型ルーティングのサンプルです"); return ok().contentType(MediaType.TEXT_HTML).render("flux",map); }
確認
- ビルドとSpring Bootを起動します
$ mvn package $ java -jar target/sample-0.0.1-SNAPSHOT.jar
- ブラウザでlocalhost:8080/f/flux にアクセスします。表示されました
考察
今回、リアクティブやノンブロッキングの特徴をまとめるとともに、その具体的な実装である
Spring WebFluxを使用してみました。
今回は、主に、APIを想定した内容でした。次回以降は、クライアント側の実装を試してみます。
参考