Spring WebFlux による真のレスポンシブプログラミング

Spring エコシステムにおいて、「リアクティブ(Reactive)」プログラミングモデルは主に Spring WebFlux によって実現される。従来の Spring MVC(Servlet API ベース)は本質的にブロッキングかつ同期的であり、リアクティブフレームワークではない

したがって厳密には:

Spring MVC は真の意味でのリアクティブインターフェースをサポートしない
✅ リアクティブな API を構築するには、Spring WebFlux を使用すべきである。

多くの開発者が「ストリーミング応答(SSE やファイルダウンロードなど)」と「リアクティブプログラミング」を混同しがちである。以下では、概念の明確化 → 比較分析 → 正しい実装例 の順で詳細に解説する。

1. 核心となる概念の整理

特性 Spring MVC Spring WebFlux
プログラミングモデル ブロッキング・同期(Servlet) ノンブロッキング・非同期・リアクティブ(Reactor)
スレッドモデル リクエストごとにスレッド(Tomcat スレッドプール) 少数スレッドで多数の同時接続を処理(Event Loop + 非同期 I/O)
リアクティブ型のサポート Mono/Flux をコアとして扱わない ネイティブで Mono<T> / Flux<T> をサポート
ストリーミング出力 SseEmitter, ResponseBodyEmitter などで対応可能 Flux を直接 SSE として出力可能
実行コンテナ Servlet コンテナ必須(Tomcat, Jetty など) Netty または Servlet コンテナで動作可能

重要な結論
- spring-boot-starter-web(= Spring MVC)を使用している場合、戻り値に Mono<String> を使っても、内部ではブロッキング実行される。
- 真のリアクティブ処理には spring-boot-starter-webflux が必要。

2. Spring MVC における「擬似リアクティブ」実装(高負荷環境では非推奨)

Spring MVC でも MonoFlux を返すことは可能だが、バックプレッシャーやノンブロッキング I/O は有効にならない。単なる非同期ラッパーに過ぎない。

例:Spring MVC で Flux を返す(注意:WebFlux ではない)

@RestController
public class PseudoReactiveEndpoint {

    @GetMapping("/pseudo")
    public Flux<String> simulateStream() {
        return Flux.just("X", "Y", "Z")
                   .delayElements(Duration.ofSeconds(1)); // Reactor API を使うが、Tomcat スレッドをブロック
    }
}

実際の挙動:

  • リクエストは Tomcat スレッドで処理される。
  • delayElements はそのスレッドを約3秒間占有する。
  • リアクティブの利点は一切得られず、コードの複雑さだけが増す。

この実装はパフォーマンス向上に寄与せず、移行期や軽微な用途に限定すべきである。

3. 真のリアクティブ API:Spring WebFlux の活用

ステップ1:依存関係の切り替え

<!-- spring-boot-starter-web を置き換える -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

これによりアプリケーションはデフォルトで Netty 上で動作し、ノンブロッキングサーバーとして機能する(Tomcat/Jetty への設定も可能だが、一部の利点が失われる)。

ステップ2:リアクティブコントローラーの実装

例1:基本的な非同期レスポンス(HTTP 短時間接続)

@RestController
public class TrueReactiveApi {

    @GetMapping("/hello")
    public Mono<String> sayHello() {
        return Mono.just("WebFlux へようこそ!")
                   .delayElement(Duration.ofMillis(100)); // ノンブロッキング遅延
    }

    @GetMapping("/countdown")
    public Flux<Integer> emitNumbers() {
        return Flux.interval(Duration.ofSeconds(1))
                   .map(i -> (int) i + 1)
                   .take(5); // 1 から 5 を1秒間隔で送信
    }
}

例2:Server-Sent Events (SSE)

@GetMapping(value = "/live-updates", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<String>> streamEvents() {
    return Flux.interval(Duration.ofSeconds(2))
               .map(seq -> ServerSentEvent.<String>builder()
                       .id(Long.toString(seq))
                       .event("update")
                       .data("Update #" + seq)
                       .build())
               .take(3);
}

出力形式(自動的に data: プレフィックス付き):

data: Update #0

data: Update #1

data: Update #2

例3:リアクティブデータベースアクセス(R2DBC 連携)

@GetMapping("/customers")
public Flux<Customer> fetchAllCustomers() {
    return customerRepository.findAll(); // ReactiveCrudRepository 実装
}

ステップ3:内部動作原理(Reactor + Netty)

  1. Reactor ライブラリ
    • Mono(0〜1要素)、Flux(0〜N要素)を提供。
    • マッピング、フィルタリング、フラットマップなどのオペレーターと、バックプレッシャー制御をサポート。
  2. ノンブロッキング I/O
    • Netty の EventLoop が少数スレッドで数万の同時接続を処理。
    • 外部通信には WebClient や R2DBC などのリアクティブクライアントが必要。
  3. エンドツーエンドのリアクティブチェーン
    Netty → WebFlux Controller → WebClient/R2DBC → 外部サービス/DB
    全てのレイヤーがノンブロッキングで、スレッドの待機が発生しない。

4. 選択基準:MVC vs WebFlux

ユースケース 推奨アプローチ
従来型 CRUD、Servlet に慣れたチーム Spring MVC
高スループット・I/O 負荷(API ゲートウェイ、リアルタイム通知) Spring WebFlux
JPA/Hibernate を使用中の既存システム WebFlux は不向き(JPA はブロッキング)
MongoDB / Cassandra / R2DBC を利用 WebFlux
WebSocket や SSE による効率的なプッシュ配信 WebFlux

重要注意
リアクティブは万能ではない。データベース、キャッシュ、外部呼び出しまで含めた全経路がノンブロッキングでなければ、性能劣化の原因となる(例:JDBC 使用や Thread.sleep() の混入)。

5. よくある誤解

誤解1:「戻り値が Mono ならリアクティブ」

  • Spring MVC では Mono は内部で DeferredResult に変換され、結果を同期的に待機するため、依然としてブロッキングモデルである。

誤解2:「WebFlux は常に MVC より高速」

  • CPU 負荷が高い処理では、スケジューリングオーバーヘッドにより逆に遅くなる可能性がある。
  • I/O 負荷が高く、同時接続数が多い場合にのみ、WebFlux のリソース効率が優位になる。

正しい認識:

WebFlux の価値は「単一リクエストの速度」ではなく、「少ないハードウェアで大量の同時接続を安定処理できる点」にある

6. まとめ

質問 回答
Spring MVC でリアクティブ API は作れるか? ❌ 不可。本質的にブロッキングモデル。
真のリアクティブを実現する方法は? Spring WebFlux + Netty + リアクティブクライアント の組み合わせ。
MVC で Flux を使う意味はあるか? ⚠️ 構文上の糖衣に過ぎず、パフォーマンス向上なし。
WebFlux を採用すべきタイミングは? 高同時接続・I/O 密集型で、かつ全経路がノンブロッキング対応済みの場合。

タグ: Spring WebFlux Reactor R2DBC SSE Netty

6月15日 18:28 投稿