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

手品に飽きた?

有休の直前に 誰かに言われました "私はマジックが好きじゃない" って。この文脈では、 マジック とは、シンプルにするためにQuarkusが裏側で行っている隠れた 作業 のことを指します。これには依存性注入やアノテーションなどが含まれます。

このようなコメントを受けるのは初めてではないし、Vert.xプロジェクトから来ているので、それは理にかなっています。Vert.x には(ほとんど)マジックがありませんが、それには正当な理由があります: マジックが多すぎるとひどいことになり、プロダクションチューニングが全く大変になります。時には、より多くの制御をして予期せぬ動作を避けたい場合があります: ただ書いたコードだけが実行され、それ以外は何もしないで欲しい、ということです。

しかし、マジックは本質的に悪いものではありません。マジックは良いことにも悪いことにも使えるパワーなのです。結局のところ、あなたのアプリケーションは、マイクロコードで力を与えられた半導体上で実行される、抽象化のマジックで力を与えられたオペレーティングシステム上の、JITのマジックで力を与えられたJava仮想マシンで動いています。マジックはありますが、それはあなたが十分な知識を持っている(または信頼している)マジックと、そうでないマジックのことです。

Quarkusにはたくさんのマジックのトリックがあると思うかもしれません。ある意味ではそうなのですが、簡単に理解でき、メモリーの最適化、起動時間の最適化、または開発者の経験の向上のいずれかにおいて強力なメリットがあります。あなたが望むマジックの量と、あなたが快適に感じるコントロールの量を決めることができます。もし自分でやりたいのであれば、依存性注入や管理クライアントを使う必要はありません。

今回の記事では、マジックの量を減らすための3つの異なるアプローチを取り上げます。マジックをほとんど使わない状態から、良い開発者体験を得るために必要なだけの状態にしていきます。このブログ記事の例は GitHubで公開されています。

ほとんどマジックがない アプローチ

QuarkusのアプリケーションはJavaアプリケーションです。そのため、どこかに public static void main(String…​ args) が存在します。Quarkusを使用する際にそのメソッドを記述する必要はありませんが、それでも便利で、アプリケーションの起動についてより多くのコントロールが可能になります。また、IDEから直接Quarkusアプリケーションを起動するのも良いでしょう。

例として、簡単な HTTP アプリケーションを実装します。派手なものは何もありません:

package me.escoffier.quarkus.nomagic;

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.QuarkusApplication;
import io.quarkus.runtime.annotations.QuarkusMain;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import io.vertx.ext.web.Router;
import org.eclipse.microprofile.config.ConfigProvider;

@QuarkusMain
public class Main implements QuarkusApplication {

    public static void main(String... args) {
        Quarkus.run(Main.class, args);
    }

    @Override
    public int run(String... args) {
        Vertx vertx = Vertx.vertx();
        Router router = Router.router(vertx);

        String message = ConfigProvider.getConfig().getValue("message", String.class);

        router.get("/").handler(rc -> rc.response().end(message));
        router.get("/bye").handler(rc -> {
            rc.response().end("bye");
            Quarkus.asyncExit();
        });

        HttpServer server = vertx.createHttpServer()
                .requestHandler(router)
                .listen(8080);

        Quarkus.waitForExit();

        server.close();
        return 0;
    }
}

完全なソースコードは ここにあります。アプリケーションにはJavaクラスが1つしかありませんが、それを調べてみましょう。

@QuarkusMain は、Quarkusがこのクラスをアプリケーションの メインエントリーポイント として使用することを示しています。 run メソッドには、アプリケーションのロジックが含まれています。このロジックについては後ほど説明します。まず、 public static void main(String…​ args) メソッドを見てください。これはアプリケーションを起動するだけです。このエントリーポイントは、IDE から直接使用できます。そう、 Quarkus.run の背後にはまだちょっとしたマジックがあります。エクステンションの初期化が行われるのは、 Vert.x を含む どの フレームワークの初期化とも異なります。

このアプリケーションは Vert.x Web と Vert.x Core に直接 依存します。Quarkusの依存関係はArcだけです(直接は使用しませんが必須です):

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-arc</artifactId>
    </dependency>

    <dependency>
        <groupId>io.vertx</groupId>
        <artifactId>vertx-web</artifactId>
        <version>3.9.5</version>
    </dependency>
</dependencies>

run メソッドに戻りましょう。これにはアプリケーション・ロジックが含まれており、ここではダミーの Vert.x アプリケーションを使用しています。 Vertx インスタンス、 Router を作成し、いくつかのルートを登録し、HTTP サーバーを起動します。アプリケーションをすぐに停止させたくないので、 終了を待ちます/bye リクエスト・ハンドラーは、アプリケーションのシャットダウンをプログラムでトリガーする方法を示しています。

このアプリケーションにはマジックはほとんどなく、ただのアノテーション1つと、普通のJavaのエントリーポイントがあるだけです。なぜむき出しのJavaプログラムを使わないのかと疑問に思うかもしれません。そのように使用しても、Quarkusには利点があります。例えば、スニペットに示されているように、組み込みの設定サポートにアクセスすることができます:

String message = ConfigProvider.getConfig().getValue("message", String.class);

設定は、 application.properties ファイルにあります。

この最初のアプローチには、いくつかの欠点があります。これはQuarkusのビルド時処理の恩恵を受けていません。ビルド時に実行されるロジックはエクステンションの中にパッケージ化されており、この場合はエクステンションは使用されません(Arcを除く)。もう一つの問題は、このアプリケーションをネイティブにコンパイルすると、ネイティブのコンパイル時にエクステンションも絡んでくるので失敗するということです。最後に、ホットリロードはうまくいきませんが、IDEから直接アプリケーションを再起動することができます。

管理された Vert.x インスタンスの使用

QuarkusはVert.xを多用しています。 quarkus-vertx-core エクステンションは、Quarkusが使用するVert.xインスタンスを管理します。そのインスタンスを直接使用することができ、Vert.xインスタンスを作成する必要はありません。インスタンスを設定する必要がある場合は、 application.properties から設定することができます。また、ネイティブパッケージングも可能になります(このエクステンションには Vert.x アプリケーションをネイティブにコンパイルするためのディレクティブが含まれているため)。

pom.xmlファイルに以下の依存関係を追加します:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-vertx-core</artifactId>
</dependency>

これにより、 run メソッドは以下のようになります:

@Override
public int run(String... args) {
    Vertx vertx = CDI.current().select(Vertx.class).get();
    Router router = Router.router(vertx);

    String message = ConfigProvider.getConfig().getValue("message", String.class);

    router.get("/").handler(rc -> rc.response().end(message + " world!"));
    router.get("/stop").handler(rc -> {
        rc.response().end("bye");
        Quarkus.asyncExit();
    });

    HttpServer server = vertx.createHttpServer()
            .requestHandler(router)
            .listen(8080);

    Quarkus.waitForExit();

    server.close();
    return 0;
}

管理されている Vert.x インスタンスを取得する方法に注目してください。 @Inject を使用することができますが、プログラムで取得することも出来、残りのコードは変更しません。ご覧になりましたか?魔法はありません! メイン メソッドを使用して、IDE から起動することができます。

quarkus-vertx-core エクステンション(またはそれに依存するエクステンション)を含まない場合、QuarkusはVert.xインスタンスを作成しません。

エクステンションを使用すると、プロパティーの設定や、ビルド時間の最適化、ネイティブイメージのコンパイルも行うことができます:

> mvn package -Dnative
...
> ./target/managed-vertx-example-1.0-SNAPSHOT-runner

しかし、やはりホットリロードはダメですね😿。

管理されたHTTPサーバーの使用

quarkus-vertx-core のエクステンションだけを使用するのではなく、HTTPサーバーをQuarkusに委任することもできます。 これは制御できないと思われるかもしれませんが、実際には、そのようなことは滅多にしませんが、必要なら、 application.properties ファイルから設定することが可能です。

`quarkus-vertx-core の代わりに、 quarkus-vertx-http を使用してください:

<dependencies>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-vertx-http</artifactId>
    </dependency>
</dependencies>

Vert.x Webに直接依存する必要はなく、含まれています。

ルートの登録は引き続き行いますが、管理されている Router を使用します:

@Override
public int run(String... args) {
    Router router = CDI.current().select(Router.class).get();

    String message = ConfigProvider.getConfig().getValue("message", String.class);

    router.get("/").handler(rc -> rc.response().end(message));
    router.get("/bye").handler(rc -> {
        rc.response().end("bye");
        Quarkus.asyncExit();
    });

    Quarkus.waitForExit();
    return 0;
}

このアプローチでは、HTTPリクエストをインターセプトするため、Quarkusのホットリロードが可能になります。アプリケーションロジックに関連するすべてのことを制御することができます。

次を使ってホットリロードを開始することができます:

> mvn quarkus:dev

最後の マジックタッチ

問題は、Quarkusの _通常の_アプリケーションからどれくらい離れているかということです。実際には、かなり近いです。RESTEasy Reactiveを使った同等のアプリケーションは、次のようなものです:

@Path("/")
public class MyResource {

    @Inject @ConfigProperty("message") String message;

    @GET
    public String hello() {
        return message;
    }

}

これまでのアプローチとは異なり、このアプローチは宣言的な(アノテーションベースの)モデルを活用しています。内部では、前のアプローチとそれほど違いはありません。Quarkusは(ルーター上で)ルートを登録し、一致するリクエストを受信すると hello メソッドを呼び出します。ルータは、 Quarkus.run メソッドの間に初期化されます。 メインエンドポイント は必要ありませんが、1つのエンドポイントを使用することができ、IDEでは便利なことが多いです。

まとめ

私たちのマジックとの関係は、私たちのバックグラウンドや経験によって異なります。Quarkusでは、どの程度のマジックを受け入れるかを決めることができます。この記事では、ほとんど魔法を使わない状態から 通常の Quarkusコードまで、4つの設定を紹介しました。それぞれのアプローチには長所と短所があります:

コントロール ビルド時最適化 ネイティブ実行可能ファイル ホットリロード

殆どマジックなし

完全

🥵

🥵

🥵

管理された Vert.x インスタンスの使用

Vert.x以外はすべて

😀、Vert.xの場合

😀

🥵

管理されたHTTPサーバーの使用

Vert.xとHTTPサーバー以外はすべて

😀、Vert.x と HTTP の場合

😀

😀

通常の Quarkus

Quarkusが管理するエンドポイント

😀

😀

😀

ニーズに合ったアプローチを選びましょう。また、設定について見てきたように、Quarkusのサービスのほとんどは、プログラム的なアプローチを使用して利用することもできます。管理されたオブジェクトを避けたい場合は、利用可能なAPIを使用してください。