リアクティブJAX-RSクライアントAPI

1前書き

このチュートリアルでは、Jersey APIを使用したリアクティブ(Rx)プログラミングに対するJAX-RSサポートを調べます。この記事は読者がJersey RESTクライアントAPIの知識を持っていることを前提としています。

反応性プログラミングの概念 についてのある程度の知識は役に立ちますが、必要ではありません。

2依存関係

まず、標準のJerseyクライアントライブラリ依存関係が必要です。

<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.27</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.inject</groupId>
    <artifactId>jersey-hk2</artifactId>
    <version>2.27</version>
</dependency>

これらの依存関係により、JAX-RSのリアクティブプログラミングサポートを利用できます。

jersey-client およびhttps://search.mavenの現在のバージョン.org/search?q = g:org.glassfish.jersey.inject%20AND 20a:jersey-hk2&core = gav[jersey-hk2]はMaven Centralで入手できます。

サードパーティのリアクティブフレームワークをサポートするために、これらの拡張機能を使用します。

<dependency>
    <groupId>org.glassfish.jersey.ext.rx</groupId>
    <artifactId>jersey-rx-client-rxjava</artifactId>
    <version>2.27</version>
</dependency>

上記の依存関係は、RxJavaの Observable ; 新しいRxJava2の __https://www.baeldung.com/rxjava-2-flowable[Flowable]に対して をサポートします次の拡張子を使用します。

<dependency>
    <groupId>org.glassfish.jersey.ext.rx</groupId>
    <artifactId>jersey-rx-client-rxjava2</artifactId>
    <version>2.27</version>
</dependency>

rxjava およびhttps://searchへの依存関係。 maven.org/search?q=a:jersey-rx-client-rxjava2%20AND%20g:org.glassfish.jersey.ext.rx[rxjava2]もMaven Centralで入手できます。

3なぜJAX-RSクライアントが必要なのか

消費するREST APIが3つあるとしましょう。

  • id-service はロングユーザーIDのリストを提供します

  • name-service は与えられたユーザーIDのユーザー名を提供します

  • shash-service は、ユーザーIDとユーザーIDの両方のハッシュを返します。

ユーザー名

それぞれのサービスに対してクライアントを作成します。

Client client = ClientBuilder.newClient();
WebTarget userIdService = client.target("http://localhost:8080/id-service/ids");
WebTarget nameService
  = client.target("http://localhost:8080/name-service/users/{userId}/name");
WebTarget hashService = client.target("http://localhost:8080/hash-service/{rawValue}");

これは人為的な例ですが、説明のために機能します。 JAX-RS仕様は、これらのサービスを一緒に利用するための少なくとも3つのアプローチをサポートしています。

  • 同期(ブロッキング)

  • 非同期(ノンブロッキング)

  • 反応性(機能的、ノンブロッキング)

3.1. 同期Jerseyクライアント呼び出しの問題

これらのサービスを利用するための一般的な方法では、ユーザーIDを取得するために id-service を利用し、返されたIDごとに name-service hash-service のAPIを順番に呼び出します。

  • このアプローチでは、各呼び出しは要求が満たされるまで実行中のスレッドをブロックします** 。これは、自明ではないユースケースでは明らかに満足のいくものではありません。

3.2. 非同期のJerseyクライアント呼び出しに関する問題

より高度な方法は、JAX-RSでサポートされている __https://docs.oracle.com/javaee/7/api/javax/ws/クライアント/InvocationCallback.html[InvocationCallback] メカニズムを使用することです。最も基本的な形式では、コールバックを get__メソッドに渡して、指定のAPI呼び出しが完了したときに何が起こるかを定義します。

私たちは今、真の非同期実行(https://stackoverflow.com/questions/26150257/jersey-client-non-blocking[スレッド効率に対するいくつかの制限付き])を得ていますが、どのように このスタイルのコードが判読不能になる可能性があるかは簡単にわかります。そして些細なシナリオ以外では扱いにくい JAX-RS specification は、このシナリオをhttps://en.wikipedia.org/wiki/Pyramid of doom__(programming)[Pyramid of Doom]として特に強調しています。

----//used to keep track of the progress of the subsequent calls
CountDownLatch completionTracker = new CountDownLatch(expectedHashValues.size());

userIdService.request() .accept(MediaType.APPLICATION__JSON) .async() .get(new InvocationCallback<List<Long>>() { @Override public void completed(List<Long> employeeIds) { employeeIds.forEachid) → { //for each employee ID, get the name nameService.resolveTemplate("userId", id).request() .async() .get(new InvocationCallback<String>() { @Override public void completed(String response) { hashService.resolveTemplate("rawValue", response + id).request() .async() .get(new InvocationCallback<String>() { @Override public void completed(String response) { //complete the business logic } //ommitted implementation of the failed() method }); } //omitted implementation of the failed() method }); }); } //omitted implementation of the failed() method }); if (!completionTracker.await(10, TimeUnit.SECONDS { logger.warn("Some requests didn’t complete within the timeout"); }

そこで、非同期で時間効率の良いコードを実現しましたが、

** 読むのが難しい

** 各呼び出しは新しいスレッドを生み出す

すべての期待値が__hash-serviceによって配信されるのを待つために、すべてのコード例で__CountDownLatch__を使用していることに注意してください。期待値は実際に配信されました。

通常のクライアントは待つのではなく、むしろスレッドをブロックしないようにコールバック内で結果に対して行われるべきことは何でもします。

====  **  3.3. 機能的で反応的なソリューション**

機能的で反応的なアプローチは私達に与えるでしょう:

** コードの読みやすさ

** 流暢なコーディングスタイル

** 効果的なスレッド管理

JAX-RSは、次のコンポーネントでこれらの目的をサポートしています。

**  ____CompletionStageRxInvokerは__CompletionStage__インターフェースをサポートします

デフォルトの反応成分として
**  __RxObservableInvokerProvider__はRxJavaの__Observable __をサポートします

**  __RxFlowableInvokerProvider__はRxJavaの__Flowable__をサポートします

他のReactiveライブラリのサポートを追加するためのhttps://jersey.github.io/documentation/latest/rx-client.html#rx.client.spi[API]もあります。

===  **  4 JAX-RSリアクティブコンポーネントサポート**

====  **  4.1.  ____CompletionStage ____ JAX-RS ** の

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletionStage.html[__CompletionStage __]とその具体的な実装の使用 -  ____https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html[CompletableFuture] - エレガントでノンブロッキングで流暢なサービス呼び出しのオーケストレーションを書くことができます。

ユーザーIDを取得することから始めましょう。

[source,java,gutter:,true]

CompletionStage<List<Long>> userIdStage = userIdService.request() .accept(MediaType.APPLICATION__JSON) .rx() .get(new GenericType<List<Long>>() { }).exceptionally((throwable) → { logger.warn("An error has occurred"); return null; });

**  ____rx()____ method呼び出しは、リアクティブ処理が開始されるポイントです。**  __exceptionally__関数を使用して、例外処理シナリオを流暢に定義します。

ここから、ネームサービスからユーザー名を取得し、名前とユーザーIDの両方の組み合わせをハッシュするための呼び出しをきれいに調整できます。

[source,java,gutter:,true]

List<String> expectedHashValues = …​; List<String> receivedHashValues = new ArrayList<>(); CountDownLatch completionTracker = new CountDownLatch(expectedHashValues.size());

userIdStage.thenAcceptAsync(employeeIds → { logger.info("id-service result: {}", employeeIds); employeeIds.forEach((Long id) → { CompletableFuture completable = nameService.resolveTemplate("userId", id).request() .rx() .get(String.class) .toCompletableFuture();

    completable.thenAccept((String userName) -> {
        logger.info("name-service result: {}", userName);
        hashService.resolveTemplate("rawValue", userName + id).request()
          .rx()
          .get(String.class)
          .toCompletableFuture()
          .thenAcceptAsync(hashValue -> {
              logger.info("hash-service result: {}", hashValue);
              receivedHashValues.add(hashValue);
              completionTracker.countDown();
          }).exceptionally((throwable) -> {
              logger.warn("Hash computation failed for {}", id);
              return null;
         });
    });
  });
});

if (!completionTracker.await(10, TimeUnit.SECONDS)) { logger.warn("Some requests didn’t complete within the timeout"); }

assertThat(receivedHashValues).containsAll(expectedHashValues);

上記のサンプルでは、​​3つのサービスの実行を流暢で読みやすいコードで構成しています。

メソッド__thenAcceptAsync__は、指定された__CompletionStage__が実行を完了した(または例外をスローした)後に、提供された関数を実行します。

連続した呼び出しはブロックされないため、システムリソースが慎重に使用されます。

**  __CompletionStage__インタフェースは、マルチステップオーケストレーション(または単一のサービス呼び出し)で、任意の数のステップを構成、順序付け、および非同期的に実行することを可能にする、さまざまなステージングおよびオーケストレーションメソッドを提供します。

====  **  4.2.  JAX-RS ** の__観測可能

____Observable ____RxJavaコンポーネントを使用するには、最初にクライアントに____RxObservableInvokerProvider ____providerを登録する必要があります(「____ ObservableRxInvokerProvider」____asはJersey仕様書に記載されていません)。

[source,actionscript3,gutter:,true]

Client client = client.register(RxObservableInvokerProvider.class);

その後、デフォルトの呼び出し元をオーバーライドします。

[source,java,gutter:,true]

Observable<List<Long>> userIdObservable = userIdService .request() .rx(RxObservableInvoker.class) .get(new GenericType<List<Long>>(){});

これ以降、標準の____Observable ____semanticsを使用して処理フローを調整できます。

[source,java,gutter:,true]

userIdObservable.subscribe((List<Long> listOfIds)→ { / define processing flow for each ID ** /});

====  4.3.  JAX-RSの____Flowable

RxJava ____Flowable ____を使うためのセマンティクスは、____ Observableのそれと似ています。 ____適切なプロバイダを登録します。

[source,java,gutter:,true]

client.register(RxFlowableInvokerProvider.class);

それから__RxFlowableInvoker__を渡します。

[source,java,gutter:,true]

Flowable<List<Long>> userIdFlowable = userIdService .request() .rx(RxFlowableInvoker.class) .get(new GenericType<List<Long>>(){});

その後は、通常の____Flowable ____APIを使用できます。

===  **  5結論**

JAX-RS仕様には、REST呼び出しをクリーンでノンブロッキングに実行するための多数のオプションがあります。

特に____CompletionStage ____インタフェースは、スレッド管理をよりきめ細かく制御するためのカスタム____Executorを提供する機会と同様に、さまざまなサービスオーケストレーションシナリオをカバーする堅牢なメソッドのセットを提供します。

この記事のコードをチェックアウトすることができますhttps://github.com/eugenp/tutorials/tree/master/spring-jersey【Githubについて】。