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

初めてのエクステンションの作成

Quarkus のエクステンションは、プロジェクトの依存関係がそうであるように、アプリケーションを強化します。エクステンションの役割は、Quarkus のパラダイムを活用して、ライブラリを Quarkus アーキテクチャにシームレスに統合することです。たとえば、ビルド時に、より多くのことを行います。これが、実績のあるエコシステムを利用して、Quarkus のパフォーマンスとネイティブコンパイルを活用することができる仕組みです。 code.quarkus.io にアクセスして、サポートされているエクステンションのリストを入手してください。

このガイドでは、サンプル Greeting エクステンション を開発します。このエクステンションは、ビジターに挨拶をするだけのカスタマイズ可能な HTTP エンドポイントを公開します。

免責事項
念のため、アプリケーションにサーブレットを追加するのにエクステンションは必要ないことを明記しておきます。このガイドは、エクステンション開発のコンセプトを説明するための簡略化された例であり、より詳細な情報が必要な場合は、 完全なドキュメントを参照してください。処理をビルド時に移動したり、ネイティブイメージのビルドを簡単にしたりする機能を代表するものではありませんのでご注意ください。

前提条件

このガイドを完成させるには、以下が必要です:

  • ざっと 30 minutes

  • IDE

  • JDK 11+ がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.8.6

  • 使用したい場合は、 Quarkus CLI

基本概念

まず最初に、いくつかの基本的な概念から始めましょう。

  • JVM モードとネイティブモード

    • Quarkus は何よりもまずJava フレームワークです。つまり、従来の JAR アプリケーションの開発、パッケージ化、実行を行うことができます。これは、JVM モード と呼ばれます。

    • GraalVM により、Java アプリケーションをマシン固有のコード (Go や C++ のように) にコンパイルすることができます。これは、ネイティブモード と呼ばれます。

Java バイトコードをネイティブシステム固有のマシンコードにコンパイルする操作は、Ahead of Time Compilation (AoT) と呼ばれています。

  • 従来型の Java フレームワークにおけるビルド時と実行時の比較

    • ビルド時は、Java ソースファイルを実行可能なもの (クラスファイル、jar/war、ネイティブイメージ) に変換するために Java ソースファイルに適用するすべてのアクションのことです。 通常、この段階はコンパイル、アノテーション処理、バイトコード生成などで構成されます。この時点では、すべてが開発者のスコープとコントロール下にあります。

    • 実行時とは、アプリケーションを実行するときに発生するすべてのアクションのことです。 これは明らかにビジネス指向のアクションを開始することに焦点を当てていますが、ライブラリや設定ファイルのロード、アプリケーションのクラスパスのスキャン、依存性インジェクションの設定、オブジェクトリレーショナルマッピングの設定、REST コントローラのインスタンス化など、多くの技術的なアクションに依存しています。

通常、Javaフレームワークは、アプリケーションの "ビジネス志向のレイヤー" を実際に起動する前に、実行時中にブートストラップを行います。ブートストラップの際、フレームワークは、リフレクションによって適切なオブジェクトをインスタンス化するために、設定、エンティティー定義、依存関係の注入バインディングなどを見つけるためにクラスパスをスキャンします。これにより、動的にメタデータを収集します。主な結果は以下の通りです。

  • アプリケーションの準備が遅れる: 実際にビジネスリクエストに対応する前に数秒待つ必要があります。

  • ブートストラップでリソース消費のピークを持つ: 制約のある環境では、実際のビジネスニーズではなく、技術的なブートストラップのニーズに基づいて、必要なリソースのサイズを決定する必要があります。

Quarkus の哲学は、これらのアクションの前倒しを行い、最終的にはビルド時に実行することで、可能な限り低速でメモリー集約的な動的コードの実行を防ぐことにあります。Quarkus のエクステンションは、お気に入りのライブラリやテクノロジーのアダプターレイヤーとして機能する Java のコードです。

Quarkus のエクステンションの説明

Quarkus のエクステンションは、2 つの部分から構成されています。

  • 実行時モジュール、エクステンション開発者がアプリケーション開発者に公開する機能を表します (認証フィルター、強化されたデータ層 API など)。実行時依存関係は、ユーザーがアプリケーションの依存関係として追加するものです (Maven POM または Gradle ビルドスクリプト)。

  • デプロイメントモジュール、ビルドの拡張フェーズで使用されます 。Quarkus の哲学に従ったライブラリを「デプロイ」する方法を説明しています。つまり、ビルド中にアプリケーションに Quarkus のすべての最適化を適用します。このデプロイメントモジュールは、GraalVM のネイティブコンパイルのための準備をする場所でもあります。

ユーザーは、エクステンションのデプロイメントモジュールをアプリケーションの依存関係として追加すべきではありません。デプロイメントの依存関係は、拡張フェーズの間に Quarkus によってアプリケーションの実行時依存関係から解決されます。

この時点で、このデプロイメントモジュールにより、多くのマジックは拡張ビルド時に起こることを理解しているはずです。

Quarkus アプリケーションブートストラップ

Quarkus アプリケーションには、3 つの異なるブートストラップフェーズがあります。

  • 拡張 (Augmentation)。ビルド時に、Quarkus エクステンションはアプリケーションのバイトコード (依存関係を含む) と設定をロードしてスキャンします。 この段階で、エクステンションは設定ファイルを読み込んだり、特定のアノテーションのためにクラスをスキャンしたりすることができます。 すべてのメタデータが収集されたら、エクステンションは、ORM、DI、または REST コントローラーの設定のようなライブラリーのブートストラップアクションを前処理することができます。 ブートストラップの結果はバイトコードに直接記録され、最終的なアプリケーションパッケージの一部となります。

  • 静的 Init。 実行時に、Quarkus は最初に、いくつかのエクステンションアクション/設定を含む最初の静的 init メソッドを実行します。 ネイティブパッケージングを行う場合、このスタティックメソッドはビルド時に前処理され、生成されたオブジェクトは最終的なネイティブ実行可能ファイルにシリアライズされるため、初期化コードはネイティブモードでは実行されません (このフェーズではフィボナッチ関数を実行すると想像してください。計算の結果は、ネイティブ実行可能ファイルに直接記録されます)。 JVM モードでアプリケーションを実行している場合、この静的 init フェーズはアプリケーションの開始時に実行されます。

  • 実行時 Init。このフェーズでは特に目立つことはありません。従来的な実行時コード実行を行います。 つまり、上記の 2 つのフェーズでより多くのコードを実行すればするほど、アプリケーションの起動が速くなります。

これで全てが説明されたので、さっそくコーディングに取り掛かりましょう

プロジェクトのセットアップ

エクステンションは、MavenまたはGradleのいずれかでビルドできます。お使いのビルドツールに応じて、以下のようにセットアップを行います。

Gradleエクステンションプラグインはまだ実験的なもので、Mavenプラグインで利用できる機能が欠けている可能性があります。

Maven のセットアップ

Quarkus は、 create-extension Maven Mojo を提供し、エクステンションプロジェクトを初期化します。

オプションの自動検出が試行されます。

  • quarkus (Quarkus Core) または quarkus/extensions ディレクトリからアクセスすると、'Quarkus Core' エクステンションのレイアウトとデフォルトが使用されます。

  • -DgroupId=io.quarkiverse.[extensionId] を使用すると、'Quarkiverse' エクステンションのレイアウトとデフォルトを使用します。

  • それ以外の場合は 'Standalone' エクステンションのレイアウトとデフォルトを使用します。

  • 将来的には他のレイアウトタイプを導入する可能性があります。

パラメータを指定せずに呼び出し、インタラクティブモードを使用することもできます。 mvn io.quarkus.platform:quarkus-maven-plugin:2.16.0.Final:create-extension -N
$ mvn io.quarkus.platform:quarkus-maven-plugin:2.16.0.Final:create-extension -N \
    -DgroupId=org.acme \ (1)
    -DextensionId=greeting-extension \  (2)
    -DwithoutTests (3)

[INFO] --- quarkus-maven-plugin:2.16.0.Final:create-extension (default-cli) @ standalone-pom ---

Detected layout type is 'standalone' (4)
Generated runtime artifactId is 'greeting-extension' (5)


applying codestarts...
🔠 java
🧰 maven
🗃 quarkus-extension
🐒 extension-base

-----------
 extension has been successfully generated in: --> /Users/ia3andy/workspace/redhat/quarkus/demo/greeting-extension
-----------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.659 s
[INFO] Finished at: 2021-01-25T16:17:16+01:00
[INFO] ------------------------------------------------------------------------
1 エクステンション groupId
2 エクステンション ID (名前空間ではない)。
3 テスト不要を示す
4 それ以上のオプションなしで、pom.xml のないディレクトリから、ジェネレーターは自動的に 'standalone' 拡張レイアウトを選択します。
5 'standalone' レイアウトでは、 namespaceId はデフォルトでは空なので、計算される実行時モジュールの artifactId は extensionId です。

Maven は、親 pom.xmlruntimedeployment モジュールから成る拡張プロジェクトを含む quarkus-greeting ディレクトリーを生成しました。

親 pom.xml

お使いのエクステンションはマルチモジュールプロジェクトです。まずは、./quarkus-greeting/pom.xml の親 POM から見ていきましょう。

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.acme</groupId>
  <artifactId>greeting-extension-parent</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>Greeting Extension - Parent</name>
  <modules>(1)
    <module>deployment</module>
    <module>runtime</module>
  </modules>
  <properties>
    <compiler-plugin.version>3.10.1</compiler-plugin.version>(2)
    <failsafe-plugin.version>${surefire-plugin.version}</failsafe-plugin.version>
    <maven.compiler.release>11</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <quarkus.version>2.16.0.Final</quarkus.version>
    <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>(3)
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-bom</artifactId>
        <version>${quarkus.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <build>
    <pluginManagement>
      <plugins>
        <plugin>(4)
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${surefire-plugin.version}</version>
          <configuration>
            <systemPropertyVariables>
              <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
              <maven.home>${maven.home}</maven.home>
              <maven.repo>${settings.localRepository}</maven.repo>
            </systemPropertyVariables>
          </configuration>
        </plugin>
        <plugin>(4)
          <artifactId>maven-failsafe-plugin</artifactId>
          <version>${failsafe-plugin.version}</version>
          <configuration>
            <systemPropertyVariables>
              <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
              <maven.home>${maven.home}</maven.home>
              <maven.repo>${settings.localRepository}</maven.repo>
            </systemPropertyVariables>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>${compiler-plugin.version}</version>
          <configuration>
            <compilerArgs>
              <arg>-parameters</arg>(5)
            </compilerArgs>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
1 エクステンションは、2 つのサブモジュール deploymentruntime を宣言しています。
2 Quarkus では、annotationProcessorPaths 設定に対応する、最新バージョンの Maven コンパイラープラグインのが必要です。
3 quarkus-bom は、オーグメンテーションの段階で Quarkus が使用している依存関係を調整します。
4 Quarkusは、テストを適切に実行するためにこれらの設定を必要とします。
5 この方法で parameters フラグを設定すると、 MCOMPILER-413 を回避できます。

Deployment モジュール

デプロイメントの ./quarkus-greeting/deployment/pom.xml を見てみましょう。

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.acme</groupId>
        <artifactId>greeting-extension-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>greeting-extension-deployment</artifactId> (1)
    <name>Greeting Extension - Deployment</name>

    <dependencies>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-arc-deployment</artifactId> (2)
        </dependency>
        <dependency>
            <groupId>org.acme</groupId>
            <artifactId>greeting-extension</artifactId> (3)
            <version>${project.version}</version>
        </dependency>
        <dependency>
          <groupId>io.quarkus</groupId>
          <artifactId>quarkus-junit5-internal</artifactId>
          <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>io.quarkus</groupId>
                            <artifactId>quarkus-extension-processor</artifactId>  (4)
                            <version>${quarkus.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

主なポイント:

1 決まりとして、配置モジュールのサフィックスは -deployment (greeting-deployment) です。
2 デプロイメントモジュールは quarkus-core-deployment アーティファクトに依存しています。どの依存関係を追加するのが便利かは後ほど見ていきます。
3 デプロイメントモジュールも実行時モジュールに依存している 必要があります
4 コンパイラーのアノテーションプロセッサーに quarkus-extension-processor を追加します。

pom.xml create-extension に加えて org.acme.quarkus.greeting.deployment.GreetingProcessor クラスも生成されました。

package org.acme.greeting.extension.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;

class GreetingExtensionProcessor {

    private static final String FEATURE = "greeting-extension";

    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE);
    }

}
FeatureBuildItem はエクステンションによって提供される機能を表します。機能の名前は、アプリケーションの起動時にログに表示されます。エクステンションは最大でも 1 つの機能を提供しなければなりません。

Build Step Processor の概念とすべてのエクステンションのデプロイメント API については、後で説明します。この時点では、このクラスは、greeting というエクステンションをデプロイする方法を Quarkus に説明していることを理解する必要があります。つまり、アプリケーションを拡張して、Quarkus のすべての利点 (ビルド時の最適化、ネイティブサポートなど) とともに greeting エクステンションを使用することになります。

実行時モジュール

最後に ./greeting-extension/runtime/pom.xml を見ていきます。

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/pom/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/xmlschema-instance">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.acme</groupId>
        <artifactId>greeting-extension-parent</artifactId>
        <version>0.0.1-snapshot</version>
    </parent>

    <artifactId>greeting-extension</artifactId>  (1)
    <name>Greeting Extension - Runtime</name>

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

    <build>
        <plugins>
            <plugin>
                <groupId>io.quarkus</groupId>
                <artifactId>quarkus-extension-maven-plugin</artifactId>  (3)
                <version>${quarkus.version}</version>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>extension-descriptor</goal>
                        </goals>
                        <configuration>
                            <deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}
                            </deployment>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>io.quarkus</groupId>
                            <artifactId>quarkus-extension-processor</artifactId> (4)
                            <version>${quarkus.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

主なポイント:

1 決まりとして、実行時モジュールはエンドユーザーに公開されるアーティファクトであるため、接尾辞 (greeting) を持たない。
2 実行時モジュールは quarkus-arc アーティファクトに依存しています。
3 Quarkus-extension-maven-plugin を追加して、ランタイムアーティファクトに含まれるQuarkusエクステンションディスクリプタを生成します。Quarkusエクステンションディスクリプタはランタイムアーティファクトを対応するデプロイメントアーティファクトとリンクさせるものです。
4 コンパイラーのアノテーションプロセッサーに quarkus-extension-processor を追加します。

Gradle のセットアップ

Quarkus は、エクステンションのGradleプロジェクトを初期化する方法を提供していません。

前述したように、エクステンションは2つのモジュールで構成されています。

  • runtime

  • deployment

この2つのモジュールから構成される、Gradleのマルチモジュールプロジェクトを作ってみます。これが、簡単な settings.gradle のサンプルファイルです。

pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
    plugins {
        id 'io.quarkus.extension' version "${quarkus.version}" (1)
    }
}

include 'runtime', 'deployment' (2)

rootProject.name = 'greeting-extension'
1 quarkusエクステンションプラグインのバージョン設定
2 runtimedeployment の両方のモジュールをインクルード

以下は、ルート build.gradle ファイルのサンプルです。

subprojects {
    apply plugin: 'java-library' (1)
    apply plugin: 'maven-publish' (2)

    group 'org.acme' (3)
    version '1.0-SNAPSHOT'
}
1 すべてのサブモジュールに java-library プラグインを適用
2 アーティファクトの公開に使用する maven-publish プラグインの適用
3 公開に使用するグループIDをグローバルに設定

io.quarkus.extension プラグインは、エクステンションをビルドするために使用されます。このプラグインは、 runtime モジュールに のみ 適用されます。

deployment モジュール

deployment モジュールは、特定のプラグインを必要としません。以下は、 deployment モジュールの最小の build.gradle ファイルの例です。

name = 'greeting-extension-deployment' (1)

dependencies {
    implementation project(':runtime') (2)

    implementation platform("io.quarkus:quarkus-bom:${quarkus.version}")

    testImplementation 'io.quarkus:quarkus-junit5-internal'
}
1 決まりとして、配置モジュールのサフィックスは -deployment (greeting-deployment) です。
2 デプロイメントモジュールは実行時モジュールに依存してい なければなりません

実行時モジュール

実行時モジュールは quarkus-arc アーティファクトに依存しています。

  • 両方のモジュールに quarkus-extension-process をアノテーションプロセッサーとして追加します。

  • エクステンションの説明ファイルを生成します。

以下は、 runtime モジュール用の build.gradle ファイルの例です:

plugins {
    id 'io.quarkus.extension' (1)
}

name = 'greeting-extension' (2)
description = 'Greeting extension'

dependencies {
    implementation platform("io.quarkus:quarkus-bom:${quarkus.version}")
}
1 io.quarkus.extension プラグインを適用します。
2 決まりとして、実行時モジュールはエンドユーザーに公開されるアーティファクトであるため、接尾辞 (greeting) を持たない。

サンプル Greeting エクステンションの基本バージョン

Greeting 機能の実装

ここでのエクステンションの素晴らしい機能は、ユーザーに挨拶をすることです。これを行うには、このエクステンションはユーザーアプリケーションで、平文 Hello で GET 動詞に応答する HTTP エンドポイント /greeting を公開するサーブレットを展開します。

この 実行時 モジュールは、ユーザーに提案したい機能を開発するところなので、そろそろ Web Servlet を作成しましょう。

アプリケーションでサーブレットを使用するには、 Undertow のようなサーブレットコンテナーが必要です。幸い、 pom.xml にインポートされる quarkus-bom には、すでに Undertow Quarkus エクステンションが含まれています。

必要なのは、 quarkus-undertow を依存関係として ./greeting-extension/runtime/pom.xml に追加することだけです。

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-undertow</artifactId>
    </dependency>

Gradleの場合は、 ./greeting-extension/runtime/build.gradle ファイルに依存関係を追加します:

    implementation 'io.quarkus:quarkus-undertow'
create-extension mojo で生成された quarkus-core-deployment への依存関係は、quarkus-undertow-deployment が既に依存しているため削除できます。

これで、runtime モジュールでサーブレット org.acme.quarkus.greeting.GreetingServlet を作成することができるようになりました。

mkdir -p ./greeting-extension/runtime/src/main/java/org/acme/greeting/extension
package org.acme.greeting.extension;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet
public class GreetingExtensionServlet extends HttpServlet { (1)

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { (2)
        resp.getWriter().write("Hello");
    }
}
1 通常通り、サーブレットを定義するには javax.servlet.http.HttpServlet の拡張が必要です。
2 HTTP GET 動詞に応答したいので、doGet メソッドをオーバーライドして、サーブレット応答の出力ストリームに Hello を記述します。

Greeting 機能のデプロイ

Quarkus マジックは、実行時のコード評価を待つのではなく、ビルド時のバイトコード生成に依存します。これは、エクステンションの deployment モジュールの役割です。落ち着いてください、バイトコードは難しいので、手動でやりたくないでしょう。 Quarkus は、作業を楽にする高レベルの API を提案しています。基本的な概念のおかげで、デプロイ時に生成するバイトコードを生成するために、生成/消費する項目と対応する手順を説明できます。

io.quarkus.builder.item.BuildItem の概念は、@io.quarkus.deployment.annotations.BuildStep でアノテーションされたメソッドのおかげで、生成または消費 (そしてある時点でバイトコードに変換したり) するオブジェクトインスタンスを表しています。このアノテーションは、エクステンションのデプロイメントタスクを説明しています。

注意

詳細は、 コアのBuildItem実装の全リストを参照してください。

生成された org.acme.quarkus.greeting.deployment.GreetingProcessor クラスに戻ります。

package org.acme.greeting.extension.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;

class GreetingExtensionProcessor {

    private static final String FEATURE = "greeting-extension";

    @BuildStep (1)
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE); (2)
    }

}
1 feature() メソッドには @BuildStep というアノテーションが付けられています。これは、Quarkus がデプロイ時に実行しなければならないデプロイタスクとして識別されます。BuildStep メソッドは、アプリケーションを拡張するための拡張時に同時に実行されます。これらは、プロデューサー/コンシューマーモデルを使用しています。このステップは、消費しているすべての項目が生成されるまで、実行されないことが保証されます。
2 io.quarkus.deployment.builditem.FeatureBuildItem は、エクステンションの説明を表す BuildItem の実装です。この BuildItem は、アプリケーションの起動時にユーザーに情報を表示するために Quarkus が使用します。

多くの BuildItem 実装があり、それぞれが展開プロセスの一面を表しています。ここではいくつかの例を紹介します。

  • ServletBuildItem: デプロイ時に生成したいサーブレット (名前、パスなど) を記述します。

  • BeanContainerBuildItem: デプロイ時にオブジェクトインスタンスを保存・取得するために使用するコンテナーについて説明します。

実現したいことに対応する BuildItem が見つからない場合は、独自の実装を作成することができます。 BuildItem は、デプロイメントの特定の部分を表すもので、できるだけ細かいものでなければならないということを覚えておいてください。BuildItem を作成する方法:

  • io.quarkus.builder.item.SimpleBuildItem デプロイ時にアイテムのインスタンスを1つだけ必要とする場合 (例: BeanContainerBuildItem、コンテナーが 1 つのみ必要な場合)。

  • 複数のインスタンスを使いたい場合は io.quarkus.builder.item.MultiBuildItem (例: ServletBuildItem、デプロイ時に多くのサーブレットを生成することができます)。

次は、HTTP エンドポイントを宣言します。これを行うには、ServletBuildItem を生成する必要があります。この時点では、 quarkus-undertow 依存関係が runtime モジュールの Servlet サポートを提案している場合は、deployment モジュールの quarkus-undertow-deployment 依存関係が io.quarkus.undertow.deployment.ServletBuildItem にアクセスできるようにする必要があることをご理解いただけたと思います。

quarkus-undertow-deployment を依存関係として ./greeting-extension/deployment/pom.xml に追加してみましょう。

    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-undertow-deployment</artifactId>
    </dependency>
create-extension mojo で生成された quarkus-core-deployment への依存は、 quarkus-undertow-deployment が既に依存しているので、これで削除できます。

Gradleの場合は、 ./greeting-extension/deployment/build.gradle ファイルに依存関係を追加します:

    implementation 'io.quarkus:quarkus-undertow-deployment'

これで org.acme.quarkus.greeting.deployment.GreetingProcessor を更新できるようになりました。

package org.acme.greeting.extension.deployment;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import org.acme.greeting.extension.GreetingExtensionServlet;
import io.quarkus.undertow.deployment.ServletBuildItem;

class GreetingExtensionProcessor {

    private static final String FEATURE = "greeting-extension";

    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE);
    }

    @BuildStep
    ServletBuildItem createServlet() { (1)
      ServletBuildItem servletBuildItem = ServletBuildItem.builder("greeting-extension", GreetingExtensionServlet.class.getName())
        .addMapping("/greeting")
        .build(); (2)
      return servletBuildItem;
    }

}
1 ServletBuildItem を返す createServlet メソッドを追加し、@BuildStep でアノテーションを付けます。これで、Quarkus はこの新しいタスクを処理して、ビルド時にサーブレット登録のバイトコードを生成します。
2 ServletBuildItem は、流暢な API を処理して GreetingExtensionServlet 型の greeting-extension というサーブレットをインスタンス化し (エクステンション runtime によって提供されるクラスです)、/greeting パスにマッピングします。

グリーティングエクステンションのテスト

Quarkus エクステンションを開発する際には、主に機能がアプリケーションに正しくデプロイされ、期待通りに動作するかどうかをテストすることを考える必要があります。そのため、テストは deployment モジュールでホストされます。

Quarkusは、既に deployment pom.xml にある quarkus-junit5-internal アーティファクトを介してエクステンションをテストするための機能を処理します (エクステンションでアプリケーションを起動する io.quarkus.test.QuarkusUnitTest ランナー)。

RestAssured (Quarkus で非常によく使われる) を使用して HTTP エンドポイントをテストします。rest-assured の依存関係を ./greeting-extension/deployment/pom.xml に追加しましょう。

    ...
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>

Gradleの場合は、 ./greeting-extension/deployment/build.gradle ファイルに依存関係を追加します:

    ...
    testImplementation 'io.rest-assured:rest-assured'

現在、create-extension Maven Mojo はテストと統合テスト構造 (-DwithoutTests をドロップ) を作成できます。ここでは、自分たちで作成します。

mkdir -p ./greeting-extension/deployment/src/test/java/org/acme/greeting/extension/deployment

エクステンションのテストを開始するには、次の org.acme.quarkus.greeting.deployment.GreetingTest テストクラスを作成します。

package org.acme.greeting.extension.deployment;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import static org.hamcrest.Matchers.containsString;

public class GreetingExtensionTest {

  @RegisterExtension
  static final QuarkusUnitTest config = new QuarkusUnitTest()
    .withEmptyApplication(); (1)

  @Test
  public void testGreeting() {
    RestAssured.when().get("/greeting").then().statusCode(200).body(containsString("Hello")); (2)
  }

}
1 Greeting エクステンションで Quarkus アプリを起動する Junit エクステンションを登録します。
2 アプリケーションが greeting エンドポイントから OK ステータス (200) で HTTP GET 要求に応答していることと、Hello を含むプレーンテキスト本文があることを確認します。

テストしてローカルの maven リポジトリーにインストールしてみましょう。

$ mvn clean install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] Greeting Extension - Parent                                        [pom]
[INFO] Greeting Extension - Runtime                                       [jar]
[INFO] Greeting Extension - Deployment                                    [jar]
[INFO]
[INFO] -----------------< org.acme:greeting-extension-parent >-----------------
[INFO] Building Greeting Extension - Parent 1.0.0-SNAPSHOT                [1/3]
[INFO] --------------------------------[ pom ]---------------------------------
...
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running org.acme.greeting.extension.deployment.GreetingExtensionTest
2021-01-27 10:24:42,506 INFO  [io.quarkus] (main) Quarkus 2.16.0.Final on JVM started in 0.470s. Listening on: http://localhost:8081
2021-01-27 10:24:42,508 INFO  [io.quarkus] (main) Profile test activated.
2021-01-27 10:24:42,508 INFO  [io.quarkus] (main) Installed features: [cdi, greeting-extension, servlet]
2021-01-27 10:24:43,764 INFO  [io.quarkus] (main) Quarkus stopped in 0.018s
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.799 s - in org.acme.greeting.extension.deployment.GreetingExtensionTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ greeting-extension-deployment ---
[INFO] Building jar: /Users/ia3andy/workspace/redhat/quarkus/demo/greeting-extension/deployment/target/greeting-extension-deployment-1.0.0-SNAPSHOT.jar
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ greeting-extension-deployment ---
...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Greeting Extension - Parent 1.0.0-SNAPSHOT:
[INFO]
[INFO] Greeting Extension - Parent ........................ SUCCESS [  0.303 s]
[INFO] Greeting Extension - Runtime ....................... SUCCESS [  3.345 s]
[INFO] Greeting Extension - Deployment .................... SUCCESS [  7.365 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  11.246 s
[INFO] Finished at: 2021-01-27T10:24:44+01:00
[INFO] ------------------------------------------------------------------------

問題なさそうですね。初めてのエクステンションの作成、おつかれさまでした。

エクステンションのデバッグ

デバッグ作業がバグを取り除く作業であるならば、プログラミングとは、バグを入れ込む作業ということになる。エドガー・W・ダイクストラ

アプリケーションビルドのデバッグ

エクステンションのデプロイはアプリケーションのビルド中に行われるため、このプロセスはビルドツールによってトリガーされます。つまり、このフェーズをデバッグしたい場合は、リモートデバッグモードを 1 に切り替えてビルドツールを起動する必要があります。

Maven

mvnDebug を使用して、Maven リモートデバッグを有効にすることができます。以下のコマンドラインでアプリケーションを起動できます。

mvnDebug clean compile quarkus:dev

デフォルトでは、Maven は、localhost:8000 上の接続を待ちます。これで、localhost:8000 にアタッチするために、IDE Remote の設定を実行することができます。

Gradle

Gradle のリモートデバッグは、デーモンモードでフラグ org.gradle.debug=true または org.gradle.daemon.debug=true を使用して有効にすることができます。以下のコマンドラインでアプリケーションを起動することができます。

./gradlew quarkusDev -Dorg.gradle.daemon.debug=true

デフォルトでは、Gradle は、localhost:5005 上の接続を待ちます。これで、localhost:5005 にアタッチするために、IDE Remote の設定を実行することができます。

エクステンションテストのデバッグ

エクステンションをテストする方法を一緒に見てきましたが、時にはうまくいかず、テストをデバッグしたい場合もあります。ここでも同じ原理で、IDE Remote の設定をアタッチするために Maven Surefire リモートデバッグを有効にします。

cd ./greeting-extension
mvn clean test -Dmaven.surefire.debug

デフォルトでは、Maven は localhost:5005 で接続を待ちます。

新しいエクステンションの使用

最初のエクステンションを構築し終えたばかりなので、Quarkus アプリケーションで使いたいと思うことでしょう。

お約束の Maven キャッチ

前のステップでまだ行っていない場合は、ローカルリポジトリに greeting-extension をインストールする必要があります。

cd ./greeting-extension
mvn clean install

次に、別のディレクトリから、我々のツールを使用して、新しいエクステンションを持つ新しい greeting-app Quarkus アプリケーションを作成します。

mvn io.quarkus.platform:quarkus-maven-plugin:2.16.0.Final:create \
     -DprojectGroupId=org.acme \
     -DprojectArtifactId=greeting-app \
     -Dextensions="org.acme:greeting-extension:1.0.0-SNAPSHOT" \
     -DnoCode

cd into greeting-app.

quarkus-greeting エクステンションをアプリケーションで使用できるようにするには、ローカルの Maven リポジトリにインストールする必要があります。

アプリケーションを実行して、 Installed Features のリストに quarkus-greeting のエクステンションが含まれていることに注目してください。

$ mvn clean compile quarkus:dev
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------------< org.acme:greeting-app >------------------------
[INFO] Building greeting-app 1.0.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ greeting-app ---
[INFO]
[INFO] --- quarkus-maven-plugin:2.16.0.Final:generate-code (default) @ greeting-app ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ greeting-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO]
[INFO] --- maven-compiler-plugin:3.10.1:compile (default-compile) @ greeting-app ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- quarkus-maven-plugin:2.16.0.Final:dev (default-cli) @ greeting-app ---
Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
2021-01-27 10:28:07,240 INFO  [io.quarkus] (Quarkus Main Thread) greeting-app 1.0.0-SNAPSHOT on JVM (powered by Quarkus 2.16.0.Final) started in 0.531s. Listening on: http://localhost:8080
2021-01-27 10:28:07,242 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2021-01-27 10:28:07,243 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, greeting-extension, servlet]

エクステンションの開発者の立場からすると、Maven の公開戦略は非常に便利で高速ですが、Quarkus は、エクステンションを使用する人々のためにエコシステムの信頼性も確保することで、さらに一歩進んでいきたいと考えています。考えてみてください。私たちは皆、メンテナンスされていないライブラリや依存関係間の非互換性 (法的な問題は除く) で、開発体験が著しく低下したことを経験していることでしょう。Quarkus Platform は、そのためのものです。

Quarkus Platform

Quarkusプラットフォームは、開発フレームワークとしてのQuarkusの主要なユースケースを対象としたエクステンションのセットであり、依存関係の競合を生じることなく、同じアプリケーション内で任意の組み合わせで安全に使用できます。アプリケーション開発者の視点では、Quarkusプラットフォームは、 io.quarkus.platform:quarkus-bom:2.16.0.Finalio.quarkus.platform:quarkus-kogito-bom:2.16.0.Final などの1つまたは複数のMaven BOMとして表現されます。これらのBOMは、依存関係のバージョン制約がグローバルに調整されているため、依存関係の競合を引き起こすことなく、同じアプリケーションに任意の順序でインポートできます。

Quarkiverse Hub

Quarkiverse Hub は、コミュニティから投稿された Quarkus エクステンションプロジェクトのリポジトリーホスティング (ビルド、CI、リリースパブリッシングのセットアップを含む) を提供する GitHub 組織です。

新しいQuarkusエクステンションを作成してQuarkusエコシステムに追加し、QuarkusコミュニティがQuarkus開発ツール( Quarkus CLIcode.quarkus.ioを含む)を使用して発見できるようにしたいと考えている場合は、 Quarkiverse HubGitHub organizationがそのための良い拠点となるでしょう。

エクステンションリクエスト 投稿を作成 (こちら) で、まだ投稿されていないかどうかを最初に確認してください) し、それをリードするように依頼することから始めることができます。

我々は、新しいリポジトリのプロビジョニングを行い、以下のように設定します。

  • 我々のツールでサポート。

  • エクステンションのために作成したドキュメントを Quarkiverse の Web サイトに公開します。

  • Quarkus エコシステム CI を使用して、最新の Quarkus Core の変更に対応してビルドするようにエクステンションを設定します。

  • プロジェクトを管理し、好きなように Maven Central にリリースする自由を与えます。

Quarkiverse Hubでホストされているエクステンションは、Quarkusプラットフォームに組み込まれる場合もあれば、組み込まれない場合もあります。

詳しくは、Quarkiverse Wikiこのブログ記事を チェックしてみてください。

まとめ

新しいエクステンションの作成は、最初は複雑な作業に見えるかもしれませんが、Quarkus の変革をもたらすパラダイム (ビルド時と実行時) を理解してしまえば、エクステンションの構造は完璧に理にかなったものになります。

いつものように、Quarkus は地道に、新機能の開発を快適にするために、Maven Mojo、バイトコード生成またはテストといった作業を簡素化するために努めています。

参考