📡 Spring Framework 講座【第33回】外部サービスと通信!〜WebClientの基本と非同期通信〜

docs

前回、アプリケーションの自動化を実現するスケジューリング機能について学びました。

Webアプリケーションは、単独で完結することは少なく、決済サービス、気象情報、認証プロバイダなど、**外部のWeb API(Webサービス)**と連携するのが一般的です。

今回は、Spring Bootで外部APIと連携する際の標準的なHTTPクライアントとして推奨されている WebClient の基本的な使い方と、その大きな特徴である**リアクティブ(非同期・ノンブロッキング)**な通信方法を学びます。


1. WebClientとは?(なぜRestTemplateではないのか)

以前のSpringでは、HTTP通信を行うために RestTemplate が使われていました。しかし、RestTemplateは同期処理(ブロッキングI/O)が基本であり、通信中にスレッドが待機してしまうため、大規模なアプリケーションでは性能上のボトルネックになりやすい問題がありました。

WebClient は、この問題を解決するために登場しました。

  • 非同期・ノンブロッキング: 通信の完了を待たずに処理を次に進めることができ、スレッドのブロックを防ぎます。これは、第31回で学んだ @Async よりもさらに効率的な通信方法です。
  • リアクティブ: Spring WebFluxというリアクティブフレームワークの一部ですが、Spring MVCアプリケーション(これまでに開発してきたアプリケーション)でも問題なく利用できます。

注意: WebClientを使うには、spring-boot-starter-webflux の依存関係をプロジェクトに追加する必要があります。

2. WebClientの基本的な使い方

WebClientは、ビルダーパターンを使ってインスタンスを生成し、Fluent API(メソッドをチェーンで繋いでいく記述)でリクエストを構築するのが特徴です。

2-1. WebClientのBean定義

外部APIのベースURLを設定した WebClient のインスタンスを Bean として登録しておくと便利です。

Java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

    @Bean
    public WebClient externalApiClient(WebClient.Builder builder) {
        // 頻繁にアクセスする外部サービスのベースURLを設定
        return builder.baseUrl("https://api.external-service.com") 
                      .build();
    }
}

2-2. データの取得(GETリクエスト)

Service層で WebClient をDIで受け取り、リクエストを実行します。

Java

import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono; // 非同期の結果を扱うためのクラス

@Service
@RequiredArgsConstructor
public class ExternalApiService {
    
    private final WebClient externalApiClient; // DIで注入

    public Mono<ExternalData> fetchData(String path) {
        
        // 1. リクエストの構築
        return externalApiClient.get() // GETリクエスト
                .uri("/v1/data/{path}", path) // パスとURI変数を設定
                .retrieve() // リクエスト実行
                
                // 2. レスポンスの処理
                .bodyToMono(ExternalData.class) // レスポンスボディを ExternalData クラスにマッピング
                
                // 3. エラーハンドリング(例: 4xx, 5xx ステータスコードの場合)
                .doOnError(e -> System.err.println("API通信エラー: " + e.getMessage()));
    }
}

3. リアクティブな戻り値:MonoとFlux

WebClientの最大の特徴は、戻り値の型が Mono または Flux になることです。これらは、リアクティブプログラミングにおけるデータストリームを表現します。

戻り値の型意味
Mono0件または1件のデータストリーム(非同期の結果)
Flux0件以上のデータストリーム(非同期のリスト、または継続的なデータ)

ControllerやServiceでは、この Mono<T>Flux<T> を受け取ります。

3-1. Mono/Fluxの処理

この MonoFlux からデータを取り出すには、**購読(Subscribe)**という操作が必要ですが、Spring MVCのControllerが戻り値としてこれらを受け取った場合、Springが自動的に購読処理を行い、データが到着次第、クライアントにレスポンスを返します

Java

@RestController
@RequiredArgsConstructor
public class DataController {
    
    private final ExternalApiService apiService;
    
    // Controllerの戻り値を Mono<T> にする
    // WebClientはノンブロッキングでデータを取得し、
    // 取得が完了した時点でSpringがレスポンスを自動で構築する
    @GetMapping("/data/{id}")
    public Mono<ExternalData> getExternalData(@PathVariable String id) {
        return apiService.fetchData(id);
    }
    
    // Listを返す場合は Flux を使う
    @GetMapping("/data/all")
    public Flux<ExternalData> getAllExternalData() {
        return apiService.fetchAllData();
    }
}

開発者は、MonoFlux をチェーンで繋いで処理を記述する(例: mapfilter)ことで、同期処理の記述感覚で非同期通信を実現できます。

4. データの送信(POSTリクエスト)

データの送信(POST/PUT)も同様に、bodyValue()bodyToMono() を使って行います。

Java

public Mono<ApiResponse> postData(RequestData data) {
    return externalApiClient.post() // POSTリクエスト
            .uri("/v1/submit")
            .bodyValue(data) // 送信するリクエストボディを設定
            .retrieve()
            .bodyToMono(ApiResponse.class);
}

✅ 本日のまとめ

  • WebClientは、Spring Bootで推奨されるHTTPクライアントであり、非同期・ノンブロッキング通信を実現し、アプリケーションの性能低下を防ぐ。
  • WebClientを使うには、spring-boot-starter-webflux の依存関係が必要である。
  • WebClientは、ビルダーパターンとメソッドチェーン(get() -> uri() -> retrieve() -> bodyToMono())でリクエストを構築する。
  • 非同期の結果は Mono(0〜1件)または Flux(0件以上)というリアクティブな型で返される。
  • Spring MVCのControllerは、MonoFlux を戻り値として受け取ると、自動で購読し、データが揃い次第レスポンスを返す。

🔔 次回予告

外部APIとの連携ができるようになりましたが、APIが混雑している場合や、一時的なネットワーク障害が発生した場合、処理が失敗したり、クライアントを長時間待たせたりする可能性があります。

次回は、外部サービスとの通信の安定性を高めるため、リトライ(再試行)やサーキットブレーカーといった耐障害性パターンを実装する方法について学びます。

次回:【第34回】外部連携の安定化!〜リトライとサーキットブレーカー〜 にご期待ください!

コメント

タイトルとURLをコピーしました