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

JSON RESTサービスの実装

JSON は今やマイクロサービス間の 共通言語 となっています。

このガイドでは、REST サービスが JSON ペイロードを利用および生成する方法を見ていきます。

RESTクライアント (JSONのサポートを含む)が必要な場合は、別のガイドがあります。

QuarkusでJSON RESTサービスを記述する方法を紹介します。 Quarkus REST(旧RESTEasy Reactive)のより詳細なガイドは こちら をご覧ください。

前提条件

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

  • 約15分

  • IDE

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

  • Apache Maven 3.9.6

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

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

アーキテクチャ

このガイドで開発するアプリケーションは非常にシンプルです: ユーザーはフォームを使用してリストに要素を追加することができ、リストが更新されます。

ブラウザとサーバー間の情報はすべて JSON 形式になっています。

ソリューション

次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。

Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.gitアーカイブ をダウンロードします。

ソリューションは rest-json-quickstart ディレクトリ にあります。

Maven プロジェクトの作成

まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します:

コマンドラインインタフェース
quarkus create app org.acme:rest-json-quickstart \
    --extension='rest-jackson' \
    --no-code
cd rest-json-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストールと使用方法の詳細については、 Quarkus CLI ガイドを参照してください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.9.3:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-json-quickstart \
    -Dextensions='rest-jackson' \
    -DnoCode
cd rest-json-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

Windowsユーザーの場合:

  • cmdを使用する場合、(バックスラッシュ \ を使用せず、すべてを同じ行に書かないでください)。

  • Powershellを使用する場合は、 -D パラメータを二重引用符で囲んでください。例: "-DprojectArtifactId=rest-json-quickstart"

このコマンドは、Quarkus REST/Jakarta RESTおよび Jackson エクステンションをインポートする新しいプロジェクトを生成し、 特に以下の依存関係を追加します:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-jackson</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-rest-jackson")

ユーザーエクスペリエンスを向上させるために、Quarkusは3つのJackson Java 8モジュール を登録しているので、手動で登録する必要はありません。

Quarkusは JSON-B もサポートしているため、JacksonよりもJSON-Bを好む場合は、代わりにQuarkus REST JSON-Bエクステンションに依存するプロジェクトを作成できます:

コマンドラインインタフェース
quarkus create app org.acme:rest-json-quickstart \
    --extension='rest-jsonb' \
    --no-code
cd rest-json-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストールと使用方法の詳細については、 Quarkus CLI ガイドを参照してください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.9.3:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-json-quickstart \
    -Dextensions='rest-jsonb' \
    -DnoCode
cd rest-json-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

Windowsユーザーの場合:

  • cmdを使用する場合、(バックスラッシュ \ を使用せず、すべてを同じ行に書かないでください)。

  • Powershellを使用する場合は、 -D パラメータを二重引用符で囲んでください。例: "-DprojectArtifactId=rest-json-quickstart"

このコマンドは、Quarkus REST/Jakarta RESTおよび JSON-B エクステンションをインポートする新しいプロジェクトを生成し、 特に以下の依存関係を追加します:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-jsonb</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-rest-jsonb")

「リアクティブ」という名前がついていますが、Quarkus RESTは従来のブロックパターンとリアクティブパターンの両方を同じようにサポートしています。

Quarkus RESTの詳細については、 専用ガイド を参照してください。

初めてのJSON RESTサービスの作成

この例では、果物のリストを管理するアプリケーションを作成します。

まず、以下のように Fruit Bean を作成してみましょう。

package org.acme.rest.json;

public class Fruit {

    public String name;
    public String description;

    public Fruit() {
    }

    public Fruit(String name, String description) {
        this.name = name;
        this.description = description;
    }
}

派手なことは何もありません。注意すべき重要なことは、デフォルトのコンストラクタを持つことはJSONシリアライズレイヤーで必須であるということです。

ここで、 org.acme.rest.json.FruitResource クラスを以下のように編集します:

package org.acme.rest.json;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Set;

import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;

@Path("/fruits")
public class FruitResource {

    private Set<Fruit> fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>()));

    public FruitResource() {
        fruits.add(new Fruit("Apple", "Winter fruit"));
        fruits.add(new Fruit("Pineapple", "Tropical fruit"));
    }

    @GET
    public Set<Fruit> list() {
        return fruits;
    }

    @POST
    public Set<Fruit> add(Fruit fruit) {
        fruits.add(fruit);
        return fruits;
    }

    @DELETE
    public Set<Fruit> delete(Fruit fruit) {
        fruits.removeIf(existingFruit -> existingFruit.name.contentEquals(fruit.name));
        return fruits;
    }
}

実装は非常に簡単で、Jakarta RESTアノテーションを使用してエンドポイントを定義するだけです。

Fruit オブジェクトは、プロジェクトの初期化時に選択したエクステンションに応じて、 JSON-B または Jackson によって自動的にシリアライズ/デシリアライズされます。

quarkus-rest-jacksonquarkus-rest-jsonb などのJSONエクステンションがインストールされている場合、 メディアタイプが @Produces@Consumes アノテーションで明示的に設定されていない限り、Quarkusはほとんどの返り値にデフォルトで application/json メディアタイプを使用します(よく知られている StringFile などのタイプには例外があり、デフォルトはそれぞれ text/plainapplication/octet-stream です)。

JSONサポートの設定

Jackson

Quarkusでは、CDI経由で取得した(そしてQuarkusのエクステンションによって消費される)デフォルトのJackson ObjectMapper は、未知のプロパティーを無視するように設定されています( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES 機能を無効にすることで)。

application.propertiesquarkus.jackson.fail-on-unknown-properties=true を設定するか、 クラスごとに @JsonIgnoreProperties(ignoreUnknown = false) を設定することで、Jackson のデフォルトの動作を復元することができます。

さらに、 ObjectMapper 、日付と時刻をISO-8601でフォーマットするように設定されています( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS の機能を無効にすることで)。

application.propertiesquarkus.jackson.fail-on-unknown-properties=true を設定するか、 @JsonIgnoreProperties(ignoreUnknown = false) を経由してクラスごとに設定することで、Jackson のデフォルトの動作を復元することができます。

また、Quarkusは、CDI Bean を介して、様々なJackson設定を非常に簡単に設定することができます。最も単純な(そして推奨される)アプローチは、 io.quarkus.jackson.ObjectMapperCustomizer 型の CDI Bean を定義し、その中で、Jackson の設定を適用することです。

カスタムモジュールを登録する必要がある場合の例は次のようになります。

import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.jackson.ObjectMapperCustomizer;
import jakarta.inject.Singleton;

@Singleton
public class RegisterCustomModuleCustomizer implements ObjectMapperCustomizer {

    public void customize(ObjectMapper mapper) {
        mapper.registerModule(new CustomModule());
    }
}

ユーザーは、選択すれば自分の ObjectMapper Beanを提供することもできます。この場合、 ObjectMapper を生成する CDI プロデューサの中で、すべての io.quarkus.jackson.ObjectMapperCustomizer Bean を手動で注入して適用することが非常に重要です。これを怠ると、様々なエクステンションによって提供される Jackson 固有のカスタマイズが適用されなくなります。

import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.arc.All;
import io.quarkus.jackson.ObjectMapperCustomizer;
import java.util.List;
import jakarta.inject.Singleton;

public class CustomObjectMapper {

    // Replaces the CDI producer for ObjectMapper built into Quarkus
    @Singleton
    @Produces
    ObjectMapper objectMapper(@All List<ObjectMapperCustomizer> customizers) {
        ObjectMapper mapper = myObjectMapper(); // Custom `ObjectMapper`

        // Apply all ObjectMapperCustomizer beans (incl. Quarkus)
        for (ObjectMapperCustomizer customizer : customizers) {
            customizer.customize(mapper);
        }

        return mapper;
    }
}
Mixinサポート

Quarkus は、io.quarkus.jackson.JacksonMixin アノテーションを介して、Jackson の Mixin サポートの登録を自動化します。 このアノテーションは、Jackson mixin として使用することを意図したクラスに配置できますが、カスタマイズすることを意図したクラスはアノテーションの値として定義されます。

JSON-B

上記のように、Quarkusでは、 quarkus-resteasy-jsonb エクステンションを使用することで、Jacksonの代わりにJSON-Bを使用するオプションを提供しています。

前項と同様のアプローチで、 io.quarkus.jsonb.JsonbConfigCustomizer beanを使用してJSON-Bを設定することができます。

例えば、 FooSerializer という名前のカスタムシリアライザを com.example.Foo タイプで JSON-B で登録する必要がある場合、以下のような Bean を追加すれば十分です。

import io.quarkus.jsonb.JsonbConfigCustomizer;
import jakarta.inject.Singleton;
import jakarta.json.bind.JsonbConfig;
import jakarta.json.bind.serializer.JsonbSerializer;

@Singleton
public class FooSerializerRegistrationCustomizer implements JsonbConfigCustomizer {

    public void customize(JsonbConfig config) {
        config.withSerializers(new FooSerializer());
    }
}

より高度なオプションは, jakarta.json.bind.JsonbConfig の Bean を( Dependent スコープで)直接提供するか,極端な場合, jakarta.json.bind.Jsonb タイプの Bean を( Singleton スコープで)提供することでしょう。後者の方法を利用する場合、 jakarta.json.bind.Jsonb を生成するCDIプロデューサーにおいて、すべての io.quarkus.jsonb.JsonbConfigCustomizer Beanを手動で注入し適用することが非常に重要です。これを怠ると、さまざまなエクステンションが提供するJSON-B固有のカスタマイズが適用されなくなります。

import io.quarkus.jsonb.JsonbConfigCustomizer;

import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.inject.Instance;
import jakarta.json.bind.JsonbConfig;

public class CustomJsonbConfig {

    // Replaces the CDI producer for JsonbConfig built into Quarkus
    @Dependent
    JsonbConfig jsonConfig(Instance<JsonbConfigCustomizer> customizers) {
        JsonbConfig config = myJsonbConfig(); // Custom `JsonbConfig`

        // Apply all JsonbConfigCustomizer beans (incl. Quarkus)
        for (JsonbConfigCustomizer customizer : customizers) {
            customizer.customize(config);
        }

        return config;
    }
}

フロントエンドの作成

それでは、 FruitResource とやり取りするための簡単なウェブページを追加してみましょう。 Quarkusは自動的に、 META-INF/resources ディレクトリの下にある静的リソースを提供します。 src/main/resources/META-INF/resources ディレクトリに、この fruits.html ファイルのコンテンツを含む fruits.html ファイルを追加します。

これで、REST サービスと対話できるようになりました。

  • Quarkusを次のように起動します:

    コマンドラインインタフェース
    quarkus dev
    Maven
    ./mvnw quarkus:dev
    Gradle
    ./gradlew --console=plain quarkusDev
  • ブラウザで http://localhost:8080/fruits.html を開きます。

  • フォームを使って新しいフルーツをリストに追加します

ネイティブ実行可能ファイルのビルド

ネイティブ実行可能ファイルは、通常のコマンド ./mvnw package -Pnative でビルドできます。

コマンドラインインタフェース
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.package.type=native

実行は簡単で、 ./target/rest-json-quickstart-1.0-SNAPSHOT-runner を実行するだけです。

その後、ブラウザで http://localhost:8080/fruits.html を開いてアプリケーションを使用します。

シリアライゼーションについて

JSONシリアライズライブラリは、Javaのリフレクションを使用してオブジェクトのプロパティーを取得してシリアライズします。

GraalVMでネイティブ実行可能ファイルを使用する場合、リフレクションで使用されるすべてのクラスを登録する必要があります。良いニュースは、Quarkusがほとんどの場合、その作業を代行してくれるということです。これまでのところ、 Fruit でさえ、リフレクトを使用するためのクラスを登録しておらず、すべてが正常に動作しています。

Quarkusは、RESTメソッドからシリアライズされた型を推論することができる場合に、何らかのマジックを実行します。以下のようなRESTメソッドがある場合、Quarkusは、 Fruit がシリアライズされると判断します。

@GET
public List<Fruit> list() {
    // ...
}

Quarkusは、ビルド時にRESTメソッドを分析することで、自動的にそのような処理を行ってくれます。

Jakarta RESTの世界でよく見られるもう一つのパターンは、 Response オブジェクトを使うことです。 Response には、いくつかの良い特典があります:

  • メソッドで何が起こるかによって異なるエンティティータイプを返すことができます (例えば LegumeError )。

  • Response の属性を設定することができます (エラーが発生した時にステータスを知ることができます):

RESTメソッドは次のようになります:

@GET
public Response list() {
    // ...
}

Response に含まれるタイプは情報がないため、Quarkusがビルド時に判断することはできません。この場合、Quarkusは必要なクラスを自動的に反映登録することができません。

これが次のセクションにつながります。

Response の利用

Fruit クラスと同じモデルに従って、JSON としてシリアライズされる Legume クラスを作成してみましょう。

package org.acme.rest.json;

public class Legume {

    public String name;
    public String description;

    public Legume() {
    }

    public Legume(String name, String description) {
        this.name = name;
        this.description = description;
    }
}

それでは、マメ科植物のリストを返すメソッドを一つだけ持つ LegumeResource REST サービスを作成してみましょう。

このメソッドは Response を返し、 Legume のリストではありません。

package org.acme.rest.json;

import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

@Path("/legumes")
public class LegumeResource {

    private Set<Legume> legumes = Collections.synchronizedSet(new LinkedHashSet<>());

    public LegumeResource() {
        legumes.add(new Legume("Carrot", "Root vegetable, usually orange"));
        legumes.add(new Legume("Zucchini", "Summer squash"));
    }

    @GET
    public Response list() {
        return Response.ok(legumes).build();
    }
}

ここで、マメ科植物のリストを表示するための簡単なウェブページを追加してみましょう。 src/main/resources/META-INF/resources ディレクトリーに、この legumes.html ファイルの内容を含む legumes.html ファイルを追加します。

ブラウザを開いて http://localhost:8080/legumes.html にアクセスすると、マメ科植物のリストが表示されます。

興味深い部分は、アプリケーションをネイティブ実行可能ファイルとして実行するときに始まります。

  • ネイティブ実行可能ファイルを次のように作成します:

    コマンドラインインタフェース
    quarkus build --native
    Maven
    ./mvnw install -Dnative
    Gradle
    ./gradlew build -Dquarkus.package.type=native
  • ./target/rest-json-quickstart-1.0-SNAPSHOT-runner で実行します

  • ブラウザで http://localhost:8080/legumes.html を開きます

そこにはマメ科類はありません。

上記のように、問題は Quarkus が、 REST エンドポイントを分析することで Legume クラスが何らかのリフレクションを必要とすることを判断できなかったことです。 JSON シリアライズライブラリーは、 Legume のフィールドのリストを取得しようとすると空のリストを取得するため、フィールドのデータをシリアライズしません。

現時点では、JSON-BやJacksonがクラスのフィールドのリストを取得しようとしたときに、そのクラスがリフレクションに登録されていない場合、例外はスローされません。GraalVMは単に空のフィールドのリストを返します。

うまくいけば、将来的にはこれが変化して、エラーがより明白になるでしょう。

Legume クラスに @RegisterForReflection アノテーションを追加することで、手動で Legume を リフレクション用に登録することができます:

import io.quarkus.runtime.annotations.RegisterForReflection;

@RegisterForReflection
public class Legume {
    // ...
}
@RegisterForReflection アノテーションは、ネイティブコンパイル時にクラスとそのメンバーを保持するようQuarkusに指示します。 @RegisterForReflection アノテーションの詳細については、 ネイティブアプリケーションのヒントのページを参照してください。

それを実行して、今までと同じ手順を踏んでみましょう。

  • Ctrl+C を叩いてアプリケーションを停止させる

  • ネイティブ実行可能ファイルを次のように作成します:

    コマンドラインインタフェース
    quarkus build --native
    Maven
    ./mvnw install -Dnative
    Gradle
    ./gradlew build -Dquarkus.package.type=native
  • ./target/rest-json-quickstart-1.0-SNAPSHOT-runner で実行します

  • ブラウザで http://localhost:8080/legumes.html を開きます

今回はマメ科の一覧が表示されました。

Reactiveになる

非同期処理を処理するために リアクティブ型 を返すことができます。Quarkusでは、リアクティブで非同期なコードを書くために Mutiny の使用を推奨しています。

Quarkus RESTは当然Mutinyと統合されています。

そして、エンドポイントは UniMulti のインスタンスを返すことができます:

@GET
@Path("/{name}")
public Uni<Fruit> getOne(String name) {
    return findByName(name);
}

@GET
public Multi<Fruit> getAll() {
    return findAll();
}

単一の結果がある場合は Uni を使用します。 Multi は、非同期的に放出される可能性のある複数の項目がある場合に使用します。

UniResponse を使用して、非同期 HTTP レスポンスを返すことができます: Uni<Response> .

Mutinyについての詳細は、 Mutiny - 直感的なリアクティブプログラミングライブラリ に記載されています。

まとめ

Quarkusを使用したJSON RESTサービスの作成は、実績のあるよく知られたテクノロジーに依存しているため、簡単に行えます。

いつものように、Quarkusは、アプリケーションをネイティブ実行可能ファイルとして実行する際に、水面下の作業をさらに簡略化しています。

覚えておくべきことは一つだけあります。 Response を使用していて、Quarkus がシリアライズされているBeanを特定できない場合は、 @RegisterForReflection を使ってアノテーションを付ける必要があります。

関連コンテンツ