The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

RESTEasy Reactiveの発表

QuarkusチームとRESTEasyチームは、QuarkusでのRESTEasyReactive統合がメインのQuarkusレポジトリ [1] に取り込まれたことを発表できることを非常に嬉しく思います。 そして次のQuarkusリリース1.11の一部になります。

皆さまにテストしていただき、できるだけ多くのフィードバックを提供していただけると幸いです。 典型的なQuarkusのやり方で、このプロジェクトは新しいエクステンションのセットとして利用可能です。

それは何ですか?

名前からお察しの通り、この作品は共通の Vert.x レイヤー上で動作するように一から書かれた 新しいJAX-RSの実装 であり、完全に リアクティブ であると同時に、 Quarkusと緊密に統合されて いるため、フレームワーク固有の作業(アノテーションスキャンやメタモデル生成など)の多くを ビルド時に 移行させることができます。

なぜ重要なのでしょうか?

最も単純な答えは、アプリケーションの REST レイヤを公開する為に広く使われている非常に強力な JAX-RS API を活用し続けながら、アプリケーションが達成できる最大スループットを大幅に向上できるということです。また、アプリケーションは起動が少し速くなり、メモリーの消費量が少し減るはずです。

ベンチマークでは、この新しいエクステンションを使った測定可能なパフォーマンスは、QuarkusのReactive Routes API(それ自体は非常に興味深いAPIですが、一般的には低レベルなものです)とほぼ同等です。

さらに、アノテーションベースのRESTレイヤーを提供する他の競合するエンタープライズJavaフレームワークと結果を比較すると、ベンチマークによっては、Quarkusは2倍ものスループットを提供しました。

他にはどんなメリットがあるのでしょうか?

おなじみのAPIと、新しいエクステンションの大幅に改善されたランタイム特性だけでは十分ではないかのように、コミュニティからの要望に基づき、あるいは開発者の経験を向上させ、仕様の粗いエッジを和らげてくれると我々が考える、本当にエキサイティングで便利な新機能(JAX-RS仕様の一部ではありません)をいくつか追加しました。これらの新機能は以下の通りです:

デフォルトでノンブロッキング

デフォルトではすべてのエンドポイントが IO スレッド上で実行されます。 @Blocking を使えばこれを変更出来ます。

スコアリングシステム

開発モードで起動すると、アプリケーションはエンドポイントのリストを表示し、エンドポイントが最適なバージョンよりも遅い理由を示すパフォーマンススコアを表示します。これは、REST のパフォーマンスを向上させる方法を見つけるのに非常に役立ちます。

リクエスト/レスポンスフィルターの新しい設計

JAX-RSのフィルターは、インターフェースを実装し、コンテキストオブジェクトをフィールドとして注入する必要がありますが、これはコストがかかり、柔軟性に欠けます。Quarkus ビルドシステムでの成功に基づき、フィルタは単にアノテーション付きのメソッドになり、全てのパラメーターは自動的に注入されるようになりました:

public class CustomContainerRequestFilter {

   @ServerRequestFilter
   public void whatever(UriInfo uriInfo, HttpHeaders httpHeaders, ContainerRequestContext requestContext) {
       String customHeaderValue = uriInfo.getPath() + "-" + httpHeaders.getHeaderString("some-input");
       requestContext.getHeaders().putSingle("custom-header", customHeaderValue);
   }
}

さらに、フィルタがブロッキング操作を行う必要がある場合は Uni<Void> を返すことができ、RESTEasy Reactive はフィルタを実行している間はイベントループスレッドをブロックしません。

最後に、まだ実施していませんが、このアプローチは他のタイプのJAX-RSプロバイダにも簡単に拡張でき、コード中で @Context を使用する必要が完全になくなります。

新しい *Param アノテーション

これらのアノテーションは、JAX-RSの @PathParam, @QueryParam などのアノテーションの代わりに、名前を指定することなく使用することを意図しています。 同じアノテーション名を再利用しないことにしたのは、JAX-RSや他のEE仕様との衝突を避けるためです。

@POST
@Path("params/{p}")
public String params(@RestPath String p,
                    @RestQuery String q,
                    @RestHeader int h,
                    @RestForm String f,
                    @RestMatrix String m,
                    @RestCookie String c) {
   return "params: p: " + p + ", q: " + q + ", h: " + h + ", f: " + f + ", m: " + m + ", c: " + c;
}
よりシンプルなパラメーターとコンテキストインジェクション

RESTEasy Reactive では、パラメーターがパスパラメーターと同じ名前であれば @PathParam@RestPath を使用する必要はありませんし、同様に、既知のすべてのコンテキストタイプに対して @Context を省略することができます。

@POST
@Path("params/{p}")
public String params(String p, UriInfo info) {
   return "params: p: " + p + ", info: " + info;
}
新しい最適なメッセージボディのリーダー/ライター

エンドポイントがサービスされるときにフィルタやインターセプタが呼び出されない場合、 リフレクションやアノテーションを必要としない、vert.x に直接書き込む、更に効率的なメッセージボディライターを使うことができます。

@Provider
public class ServerVertxBufferMessageBodyWriter extends VertxBufferMessageBodyWriter
       implements ServerMessageBodyWriter<Buffer> {

   @Override
   public boolean isWriteable(Class<?> type, ResteasyReactiveResourceInfo target, MediaType mediaType) {
       return true;
   }

   @Override
   public void writeResponse(Buffer buffer, ServerRequestContext context) {
       context.serverResponse().end(buffer.getBytes());
   }
}
デフォルトのコンテンツタイプ

String を返すエンドポイントは、デフォルトで text/plain を生成します。JSON やその他の型についても同様のことを行う予定です。

CDIの統合

JAX-RSの@Contextを介したインジェクションはすべてArcに委譲されます。これにより、ユーザーは、ArcがQuarkusの他のすべての部分にもたらすビルドタイムインジェクションのメリットを得ることができます。

クラスごとの例外マッパー

JAX-RS仕様では、特定のJAX-RSリソースクラスに対して異なる例外を処理する方法はありません - すべての例外のマッピングはグローバルな方法で行われます。

しかし、RESTEasy Reactive では、次のようなことができます:

@Path("first")
public class FirstResource {

   @GET
   @Produces("text/plain")
   public String throwsVariousExceptions(@RestQuery String name) {
       if (name.startsWith("IllegalArgument")) {
           throw new IllegalArgumentException();
       } else if (name.startsWith("IllegalState")) {
           throw new IllegalStateException("IllegalState");
       } else if (name.startsWith("My")) {
           throw new MyException();
       }
       throw new RuntimeException();
   }

   @ServerExceptionMapper({ IllegalStateException.class, IllegalArgumentException.class })
   public Response handleIllegal() {
       return Response.status(409).build();
   }

   @ServerExceptionMapper(MyException.class)
   public Response handleMy(SimpleResourceInfo simplifiedResourceInfo, MyException myException,
           ContainerRequestContext containerRequestContext, UriInfo uriInfo, HttpHeaders httpHeaders, Request request) {
       return Response.status(410).entity(uriInfo.getPath() + "->" + simplifiedResourceInfo.getMethodName()).build();
   }
}

特定のリソースクラスの例外処理をカスタマイズする為です。

また、@ServerExceptionMapper`は、JAX-RSが`ExceptionMapper`を使うのと同じように、グローバルな方法で例外を扱うことができることにも注意する必要があります。 そのためには、Resourceクラスに属さないメソッドに `@ServerExceptionMapper をアノテーションするだけで大丈夫です。

他のエクステンションでも動作しますか?

勿論!

既存のquarkus-resteasyエクステンションと統合するエクステンションは、quarkus-resteasy-reactiveエクステンションとも統合されます。そのため、CDISecurityMetricsJSONQuteBean Validation、_OpenAPI_を使い続けることができ、素晴らしいアウトオブボックスと完全な開発体験を楽しむことができます。

どうやって試してみたらいいのでしょうか?

このプロジェクトはQuarkus masterブランチにありますので、試してみたい方は、https://github.com/quarkusio/quarkus/blob/master/CONTRIBUTING.md#building-master[this]のようにソースからQuarkusをビルドし、https://github.com/quarkusio/quarkus/blob/master/CONTRIBUTING.md#updating-the-version[this]のように適切なBOMとバージョンを使用してください。

さらに、バージョン 999-SNAPSHOT をQuarkusのバージョンとして指定し、BOMとして quarkus-universe-bom の代わりに quarkus-bom を使用することで、Mavenのスナップショットを使用することもできます(Quarkusのスナップショットビルドは1日1回Sonatypeにアップロードされるため)。 Mavenでスナップショットバージョンを有効にするには、さまざまな方法があります。 This StackOverflowの回答では、プロジェクト単位でもグローバルでも使える設定が示されています。

利用可能な RESTEasy Reactive エクステンションは以下の通りです:

  • quarkus-resteasy-reactive

  • quarkus-resteasy-reactive-jackson

  • quarkus-resteasy-reactive-jsonb

  • quarkus-resteasy-reactive-qute

これらのエクステンションは、既存の quarkus-resteasy* エクステンションと同等ですので、アプリケーション内で quarkus-resteasy-jackson から quarkus-resteasy-reactive-jackson に切り替えるだけで、Jackson 統合による RESTEasy Reactive を試すことができるようになります。

さらに、JAX-RSクライアントを使用する必要がある場合は、quarkus-jaxrs-client エクステンションを使用することができます (これは宣言型MicroProfile RESTクライアントではなく、JAX-RS仕様で定義されたプログラマティッククライアントです)。

どんなことに気をつければいいのでしょうか?

  • 最初に注意しなければならないのは、当面の間、この一連のエクステンションは実験的なものと考えられているということです。このプロジェクトはJAX-RS TCKのほぼ全体を通過していますが、まだ最初のリリースに過ぎませんので、典型的な実績のあるライブラリよりも多くのバグがあるかもしれないことを覚えておいてください。 これは最初のリリースですが、近い将来、このライブラリがQuarkusのデフォルトのRESTレイヤーになることを見通しています。

  • 新機能のセクションで述べたように、デフォルトではリクエストはイベントループスレッド上で処理されます。これは最大のスループットを保証しますが、これらのスレッドでブロッキング作業を行うべきではないことも意味します。ブロッキング IO を使用する場合 (例えば、Hibernate Panache でデータベースにアクセスするなど)、メソッドまたはクラスのいずれかで @Blocking アノテーションを必ず使用してください。 これにより、リクエストがワーカースレッドで処理されるようになります。言うまでもなく、このデフォルトについてのフィードバックも非常に興味を持っています。

  • ドキュメントはまだありません。ドキュメントは正式な1.11リリースの前に追加され、徐々に強化されていく予定です。 このメールには開始に必要な情報がすべて含まれているはずですが、何か問題が発生した場合は、通常のチャネル(Zulip チャット、メーリングリスト、GitHub Issues、StackOverflow)のいずれかで支援を受けることができます。

どのJAX-RSの機能が欠けていますか?

私たちは、JAX-RS TCK が必要とする機能をすべて実装するのではなく、ほとんどのユーザーが最新の REST レイヤから必要とするものに焦点を当てることにしました。 そのため、RESTEasy Reactive には XML サポートがなく、仕様の様々な難解な機能もサポートされていません(javax.activation.DataSource, javax.annotation.ManagedBean, javax.ws.rs.core.StreamingOutput のようなもの)。

さらに、最初のリリースには、 (専用のエクステンションが存在する) 新しい JAX-RS Clientをベースにした MicroProfile REST Client の実装が含まれていないことも注目に値する。これは近い将来に変更される可能性が高い。

次のステップ

新しいエクステンションは通常の 1.11 リリースで利用可能になりますが、新しいエクステンションを出来る限り簡単に試せるように、そしてフィードバックを簡単に出来るように、私たちは 1.11.0.Alpha1 リリースを行うことを検討しています。

プロジェクトのさらなる改善の為に、RESTEasy ReactiveをQuarkusで使用した感想や経験を是非お聞かせ下さい。


1. 現在、メインのRESTEasy Reactiveコンポーネントは、メインのQuarkusリポジトリのhttps://github.com/quarkusio/quarkus/tree/main/independent-projects/resteasy-reactiveにあります。 ただし、問題が解決すると、このコードはhttps://github.com/resteasy/resteasy-reactiveに移動する予定です。 この動きは、quarkus-resteasy-reactiveエクステンションのユーザーにはまったく影響を与えないはずです。将来、誰かがこのブログ投稿を読んで見つからない場合は、注意が必要です。