API呼び出しをRestTemplateからWebClientに切り替えた話
どんな話?
spring-bootで作成されているマイクロサービス間のAPI通信のクライアントをRestTemplateからWebClientに切り替えてみましたという話。
なぜ切り替えたのか、どのように対応しているのかといった内容を書きます。
なぜ切り替えたのか?
ビジネスロジックの中でいくつか別サービスへの通信を行っているが、いくつか非同期で通信を行える箇所があった。
メッセージキューを利用したりと徐々に非同期化を図っていたが、今回のケースでは解決法としてWebClientを利用するのが適してそうなこともあり試してみた。
そもそもRestTemplateは2022年4月現在でメンテナンスモードとなっており公式でもWebClientの利用が推奨されていたため、近い将来切り替えたいという話も上がっていた。
切り替え前の状況
ビジネスロジック内の1処理としてhttp通信での別サービスの呼び出しを行っている。
この通信自体は即時性が求められるものではなく、非同期での連携で問題ないものではあったが同期的に通信処理を行っていた。
ただし連携開始してから、たびたび通信が不安定になるケースが発生しており、これに起因しての不具合が発生する事がわかった。
そのため非同期連携に切り替えようと思いどんな方法が良いかなと考えたところ、WebClientでノンブロッキングでの連携が適していると考え切り替えを実施した。
切り替えについて
実現したいこと
実装例
APIクライアントの定義例
@Component public class ServiceApi { private final WebClient webClient; public ServiceApi() { this.webClient = WebClient.builder() .baseUrl("URL") .build(); } public Mono<Void> create() { log.info("cacheUserPlan request={}", request); return this.webClient .post() .uri("URL") .retrieve() .bodyToMono(Void.class) .retryWhen( Retry.fixedDelay(2, Duration.ofSeconds(5)) // タイムアウトエラーが発生した場合には5秒間隔でリトライを行う(2回までAPI呼び出しを試みる) .filter( throwable -> throwable instanceof WebClientRequestException && (throwable.getCause() instanceof ConnectException || throwable.getCause() instanceof TimeoutException))); } }
API呼び出し側の実装例
今回はビジネスロジックからの該当のAPI呼び出し部分のみノンブロッキングにしたかったため呼び出し側でsubscriberを利用 (Monoのままプレゼンテーションでレスポンスはしない)
ServiceApi .create() .subscribe( response -> { // API呼び出しが成功した場合の処理 }, exception -> { // API呼び出しでエラーが発生した場合の処理 });
参考にさせていただいた情報
リトライのテストについて
リトライ周りのテストをどう書くのが良いか分からず少しハマった。
調べてみた限りMockWebServerを利用してモックサーバにAPI接続して試す方法が良く紹介されていたのでそちらを利用してみることにした。
参考にさせていただいた情報
- How to test Spring WebClient retry when?(stack overflow)
- tokuhirom /java-handbook
- タイムアウトの場合のテストどうやってやるのかについてとても参考になった
今後について
切り替え後はしばらく様子を見てエラーハンドリングやリトライ設定などを今後必要に応じて見直していきたい。
リトライについてはjitterを利用したexponential backoffの利用なども考えていきたい。