昨今、マイクロサービスアーキテクチャによるアプケーション開発がとても注目を集めています。マイクロサービスアーキテクチャでは、文字通り小さなサービスを多数組み合わせて1つのアプリケーションを構成することから、個々のサービスには高いリソース効率が求められます。また、従来のモノリシックなアプリケーションと比べて、サービス間通信が多く発生することから、高い処理能力(高スループット)も必要です。
Javaで高いリソース効率性と高い処理能力を持つアプリケーションを開発するためのフレームワークの一つとして、Spring Frameworkファミリーの「Spring WebFlux」があります。
本連載では、Spring WebFluxによるアプリケーションの作り方をはじめ、Spring WebFluxを理解する上で重要となる「リアクティブプログラミング」や、Spring WebFluxベースのHTTPクライアントである「WebClient」、Spring WebFluxと親和性の高いデータベースアクセスの仕組みである「R2DBC」を解説していきます。
なお、本連載では、Java/Spring Framework(Spring MVC)での開発経験がある開発者を想定しています。Spring Frameworkの開発経験がない場合には、Spring Frameworkを使った開発のベストプラクティス集「TERASOLUNA Server Framework Developement Guideline」のチュートリアルを実施することをお薦めします。
Spring WebFluxの概要
Spring FrameworkにはWebアプリケーションを作るためのフレームワークとして「Spring MVC」と「Spring WebFlux」の2つがあります。Spring MVCとSpring WebFluxの技術スタックには下図のような違いがあります。
Spring MVCはサーブレットをベースとしており、Tomcatなどのサーブレットコンテナの上で動作します。それに対して、Spring WebFluxはNettyなどのノンブロッキングI/Oベースのアプリケーションサーバの上で動作します。
ノンブロッキングI/O
「ノンブロッキングI/O」とは、ネットワークアクセスやDBアクセスなどのI/O処理の呼び出しに対して、読み書きができる場合は処理を行い、読み書きができない場合には即座に処理を戻すという仕組みです。読み書きができない場合に別の処理を行わせることができるため、CPUを効率的に利用することが可能になります。
Spring WebFluxでは、このノンブロッキングI/Oの仕組みを活かし、1つのスレッドで複数のリクエストを処理します。そのため、同時処理数分だけスレッドを必要とするSpring MVCなどと比べて、必要となるスレッド数が少なくなり、必要なメモリサイズも小さくなります。
また、ノンブロッキングな処理を実装しやすくするための仕組みとして、Spring WebFluxでは「Reactor」によるリアクティブプログラミングを採用しています。リアクティブプログラミングとは、データに着目したイベント駆動型のプログラミングの一種で、通知されるデータを受け取って処理を行うハンドラを実装することによって連続的なデータを処理する手法です。Reactorはリアクティブプログラミングを実現するためのライブラリの一つであり、ノンブロッキングで非同期なリアクティブプログラミングの仕組みを提供しています。
Spring WebFluxではReactorのAPIを使うことによって低レベルなノンブロッキングI/Oの仕組みを隠蔽し、より抽象度の高いかたちでノンブロッキングな処理を実装できるようにしています。
Spring WebFluxのユースケース/事例
Spring WebFluxの特徴は、リソース効率性と処理能力の高さです。そのため、冒頭でも述べたようにマイクロサービスアーキテクチャのシステムに適していると言えます。国内では、クックパッドのシステムにおいてAPIオーケストレーション層の実装として採用されている事例が開発者ブログで紹介されています。
また、マイクロサービスアーキテクチャではなくても、同時接続数が多いシステムであれば、Spring WebFluxを採用することでインフラコストを抑えることが可能です。例えば、LINEの内部では一部Spring WebFluxを使用しているとの事例が公開されています。そのほか、同時接続数が多いものとしてはIoTの領域において多数のセンサデータを処理するシステムなどが挙げられます。
Spring WebFluxによるHello Worldアプリケーションの作成
それでは実際にSpring WebFluxを使ったHello Worldアプリケーションを作成してみましょう。ここでは、「Spring Initializr」を使ってプロジェクトのひな型を作成します。
まず、Spring Initializrにアクセスします。 画面左側の各種パラメータを下記のように指定します。
パラメータ | 設定値 |
---|---|
Project | Maven Project |
Language | Java |
Spring Boot | 2.3.4 |
Group | com.example |
Artifact | demo |
Name | demo |
Description | Demo project for Spring Boot |
Package Name | com.example.demo |
Packaging | Jar |
Java | 15 |
画面右上の「ADD DEPENDENCIES」を選択し、表示されるダイアログの中から「Spring Reactive Web」を選択します。 そして、画面下部の「GENERATE」を選択し、プロジェクトのひな型をZIPファイルとしてダウンロードします。
次に生成したプロジェクトひな型の中身を確認してみましょう。ZIPファイルを解凍すると以下のような構成になっています。
ここで「pom.xml」の内容を確認してみます。すると、以下のように依存関係としてspring-boot-starter-webfluxが含まれています。これがSpring BootでSpring WebFluxのアプリケーションを作る際のスターターになります。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
そのほか、テストスコープの依存関係としてreactor-testも含まれています。
続いて、Hello Worldを返すエンドポイントを実装していきます。Spring WebFluxには、アノテーションとクラスを使った実装方法と、HandlerFunctionやRouterFunctionなどの関数を利用した関数型プログラミングによる実装方法がありますが、本連載では、アノテーションとクラスを使った実装方法で解説します。また、説明上、一部リアクティブプログラミングの用語(Reactor、Monoなど)が出てきますが、これらについては次回詳しく解説します。
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
@RestController // (1)
public class HelloWorldController {
@GetMapping("/greeting") // (2)
public Mono<String> greeting() { // (3)
return Mono.just("Hello World!"); // (4)
}
}
項番 | 説明 |
---|---|
1 | Spring Web MVCと同様、@RestControllerアノテーションを付与することでREST APIのControllerクラスとして指定することができます |
2 | Spring Web MVCと同様、@GetMappingアノテーションを付与することでGETリクエストに対応するエンドポイントを指定することができます |
3 | メソッドの返り値の型として、Reactorの型であるMonoクラスを指定しています |
4 | "Hello World!"の文字列を出力するMonoを生成して返却します |
それではアプリケーションを起動してみましょう。 ターミナルを開き、プロジェクトのルートディレクトリ上でmvnwコマンドを実行します。
$ ./mvnw spring-boot:run
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------------< com.example:demo >--------------------------
[INFO] Building demo 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] >>> spring-boot-maven-plugin:2.3.4.RELEASE:run (default-cli) > test-compile @ demo >>>
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:resources (default-resources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:compile (default-compile) @ demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/media/Downloads/demo/target/classes
[INFO]
[INFO] --- maven-resources-plugin:3.1.0:testResources (default-testResources) @ demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /Users/media/Downloads/demo/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.1:testCompile (default-testCompile) @ demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /Users/media/Downloads/demo/target/test-classes
[INFO]
[INFO] <<< spring-boot-maven-plugin:2.3.4.RELEASE:run (default-cli) < test-compile @ demo <<<
[INFO]
[INFO]
[INFO] --- spring-boot-maven-plugin:2.3.4.RELEASE:run (default-cli) @ demo ---
[INFO] Attaching agents: []
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.4.RELEASE)
2020-10-30 00:58:50.233 INFO 6663 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on GeorgeMedianoMacBook-Pro.local with PID 6663 (/Users/media/Downloads/demo/target/classes started by media in /Users/media/Downloads/demo)
2020-10-30 00:58:50.236 INFO 6663 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2020-10-30 00:58:51.609 INFO 6663 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port(s): 8080
2020-10-30 00:58:51.621 INFO 6663 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 2.058 seconds (JVM running for 2.432)
標準出力の内容を見てみると、Nettyサーバが起動していることが分かります。 続いて、curlで先ほど実装したエンドポイントにアクセスし、「Hello World!」の文字列が返ってくることを確認してみましょう。
$ curl http://localhost:8080/greeting
Hello World!
* * *
今回はSpring WebFluxの概要と簡単なアプリケーションの作り方を解説しました。次回は、Spring WebFluxを使う上で欠かせない要素であるリアクティブプログラミングについて詳しく解説します。
著者紹介
伊藤 司(Ito Tsukasa) - NTTデータ
テスト技術のR&D、システム開発のプロセス標準整備などを経て、現在は一般企業向けの業務システム開発に従事。アプリケーションのアーキテクチャ設計のほか、クラウド基盤の設計・構築なども行っている。
また、システム開発の業務のかたわら、アプリケーションフレームワークやコンテナ、Kubernetesなどの技術検証も行っている。