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

Quarkusのカスタムエクステンションに関する問題の解決

時折、「誰が高速起動を必要としているのか」、「メモリは十分にある」、「ライブリロードの意味は何か」といった理由で、Quarkusの必要性が理解できないと主張するツイートや記事を目にすることがあります。

たとえ高速起動が必要なくとも、後者がいかに開発ワークフローをより効率的にするか、また前者がいかに後者を可能にするかを説明し、これらの議論を否定する記事を書くこともできます。しかし、このブログ記事のために、これらの人たちが全く正しく、これらはQuarkusを使用する良い理由ではないとしましょう。

さて、どうしましょう? <ココに好きなフレームワークを入れてください> に戻る?そうはいきません…​。

Quarkusは、黒魔術や遅延ローディングのトリックを使って高速起動と低メモリフットプリントを実現したのではなく、Javaアプリケーションのブートストラップ方法を全面的に再考しました。Quarkusの全体的なポイントは、できるだけ多くの作業をビルド時に移動させることです。この移動のために、我々はビルド時に作業を進めるためのフレームワークを作成し、Quarkusのエクステンションで活用できるようになりました。

Quarkusのエクステンション?それは大変そうですね?

いいえ、そんなことはありません。独自のエクステンションを簡単に開発することができ、通常では考えられないような問題を非常にシンプルな方法で解決することができます。

先週、私たちのユーザーの一人(ヘイ、Juan!)が、Zulipでこんな質問をしました。

こんにちは!いくつかの基準でクラスを見つけ、それらを依存性注入コンテキストに追加する方法を理解しようとしています。名前が "MessageTransformer "で終わるすべてのクラスを見つけて、コンテキストに追加したいのですが、それらのクラスは外部のライブラリで見つけたいので、アノテーションを追加することはできません。

この問題を解決するために、カスタムエクステンションを開発する方法を見てみましょう。

エクステンションの作成

エクステンションの作成は以下のようにとても簡単です。

mvn io.quarkus:quarkus-maven-plugin:create-extension -DwithoutTests

groupId - デフォルトの org.acme - とエクステンションID - message-transformers-as-beans を要求されます。

その後、お好きなIDEに新しいエクステンションをインポートすることができます。

エクステンションの構造

エクステンションについては、いろいろと言いたいことがありますが、このブログ記事の文脈では、短くまとめます。エクステンションは、3つのMavenモジュールで構成されています。

  • 親モジュール - 特に注意すべきことはありません

  • デプロイメントモジュール - 今回のブログ記事ではこのモジュールを紹介します。

  • ランタイムモジュール - このブログ記事では変更しません。

簡単に説明しますと、デプロイメントモジュールはビルド時に使用されるもので、ランタイムモジュールは実行時に使用されるものです。

今回のケースでは、新しいBeanを宣言したいのですが、これはビルド時に行うものなので、デプロイメント・モジュールが必要になります。

プロセッサーとビルドステップ

deployment モジュールを見ると、 MessageTransformersAsBeansProcessor 、その中に @BuildStep のアノテーションが付いたメソッドがあるのがわかると思います。

Quarkusのビルドには、これらのビルドステップが含まれており、依存性注入によるコンシューマー/プロデューサーモデルに従っています。消費されるアイテムと生産されるアイテムは、 BuildItem と呼ばれます。

自動的に生成されるビルドステップはわかりやすいです。これは、Quarkusの起動時に消費される FeatureBuildItem を生成し、起動時にQuarkusが表示するリストにエクステンションの名前が表示されます。

INFO  [io.quarkus] my-app 1.0.0-SNAPSHOT on JVM (powered by Quarkus 1.13.2.Final) started in 0.221s.
INFO  [io.quarkus] Profile prod activated.
INFO  [io.quarkus] Installed features: [cdi, message-transformers-as-beans]

Jandexインデックス

これでスキャフォールド(足場)ができたので、何を実現したいかを少し考えてみましょう。あるパッケージの中で、名前が MessageTransformer で終わるクラスをすべて見つける必要があります。

Quarkusの重要な前提は、アプリケーションが閉じた世界に存在するということです。実行時に動的にjarをQuarkusアプリケーションに追加して、それが動作することを期待することはできません。

これは制限とも言えますが、様々な可能性を秘めています。その1つが、クラスとそのアノテーションにインデックスを付けて、簡単に調べられるようにすることです。

Jandexをベースにしたこの指標は、Quarkusのブートストラップにおいて非常に重要な役割を果たしています。

Jandexのインデックスは、すべてのクラスを含んでいるわけではなく、デフォルトでは、アプリケーションクラスと、事前に構築されたインデックスまたは空の META-INF/beans.xml を含む依存関係に限定されています。

今回のケースでは、外部依存関係にあるクラスをリストアップしたいので、インデックスに追加する必要があります。これは、 MessageTransformersAsBeansProcessor にビルドステップを追加することで簡単に行うことができます。

@BuildStep
IndexDependencyBuildItem indexExternalDependency() {
    return new IndexDependencyBuildItem("my.group.id", "my-artifact-id");
}

これにより、 my.group.id:my-artifact-id jarの内容がインデックスに追加されます。

追加 Bean の宣言

クラスのインデックスができたので、CDI Beanにしてみましょう。

これは、別のビルドステップを追加することで実現できます。

@BuildStep
void declareMessageTransformersAsBean(CombinedIndexBuildItem index, (1)
        BuildProducer<AdditionalBeanBuildItem> additionalBeans) { (2)
    List<String> messageTransformers = index.getIndex().getKnownClasses().stream() (3)
            .filter(ci -> !Modifier.isAbstract(ci.flags())) (4)
            .map(ci -> ci.name().toString()) (5)
            .filter(c -> c.startsWith("my.package.")) (6)
            .filter(c -> c.endsWith("MessageTransformer")) (7)
            .collect(Collectors.toList());

    additionalBeans.produce(new AdditionalBeanBuildItem.Builder() (8)
            .addBeanClasses(messageTransformers)
            .setUnremovable() (9)
            .setDefaultScope(DotNames.APPLICATION_SCOPED) (10)
            .build());
}
1 Jandexインデックスの消費
2 追加のBeanプロデューサーを投入
3 インデックスからすべての既知のクラスを取得
4 抽象クラスの除外
5 クラスのFQCNを取得
6 対象のルートパッケージのクラスのみを保持
7 MessageTransformer のみを保持
8 AdditionalBeanBuildItem を生産
9 プログラムでしか消費されない場合に、ArCがBeanを取り除かないようにするために、Beanを取り除けないようにする。
10 デフォルトのスコープを @ApplicationScoped に設定 - 任意のCDIスコープにすることも可能

このビルドステップでは、ルートパッケージ my.package から、名前が MessageTransformer で終わる非抽象クラスは、 @ApplicationScoped CDI Bean になります。

さらに、これらの作業はすべてビルド時に行われるので、ランタイムにクラスパス全体をスキャンする必要はありません。

通常、インターフェイス、スーパークラス、アノテーションでインデックス内のクラスを検索します。これは、インデックス全体をクロールして名前でフィルタリングするよりも、脆くなく、速い方法です。

しかし、ここでのポイントはユーザーの制約であり、外部依存を調整するという選択肢はありませんでした。

以上です!皆さん、よろしくお願いします。

もちろん、これは非常に単純な例であり、Quarkusのエクステンションを使えば、もっと複雑なことができます。

しかし、このブログ記事の目的は、現実の問題を解決するために、私たちのエクステンションフレームワークを簡単に利用できることを示すことでした。そして、 10分ほどのコーディングで、私たちの問題は解決しました

次は?

参加のお誘い

私達は皆様からのフィードバックに重きを置いています。バグ報告、改善要望を是非お願いします。一緒に素晴らしいものを作り上げていきましょう!

Quarkusユーザーの場合でも、単に興味を持っているだけの場合でも、恥ずかしがらずにコミュニティに参加して下さい!: