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サービスを記述するための入門書です。RESTEasy Reactiveに関するより詳細なガイドは、 こちら をご覧ください。

前提条件

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

  • 約15分

  • IDE

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

  • Apache Maven 3.8.1+

  • 使用したい場合、 Quarkus CLI

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

アーキテクチャ

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

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

ソリューション

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

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

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

Maven プロジェクトの作成

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

CLI
quarkus create app org.acme:rest-json-quickstart \
    --extension=resteasy-reactive-jackson \
    --no-code
cd rest-json-quickstart

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

Quarkus CLIのインストール方法については、Quarkus CLIガイドをご参照ください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-json-quickstart \
    -Dextensions="resteasy-reactive-jackson" \
    -DnoCode
cd rest-json-quickstart

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

このコマンドは、RESTEasy/JAX-RSと Jackson エクステンションをインポートする新しいプロジェクトを生成し、特に以下の依存関係を追加します。

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

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

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

CLI
quarkus create app org.acme:rest-json-quickstart \
    --extension=resteasy-reactive-jsonb \
    --no-code
cd rest-json-quickstart

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

Quarkus CLIのインストール方法については、Quarkus CLIガイドをご参照ください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:2.11.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-json-quickstart \
    -Dextensions="resteasy-reactive-jsonb" \
    -DnoCode
cd rest-json-quickstart

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

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

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

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

RESTEasy Reactiveの詳細については、 専用ガイドをご参照ください。

初めての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 javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.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;
    }
}

The implementation is pretty straightforward, and you just need to define your endpoints using the JAX-RS annotations.

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

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

JSONサポートの設定

Jackson

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

You can restore the default behavior of Jackson by setting quarkus.jackson.fail-on-unknown-properties=true in your application.properties or on a per-class basis via @JsonIgnoreProperties(ignoreUnknown = false).

さらに、 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 javax.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.jackson.ObjectMapperCustomizer;

import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.inject.Singleton;

public class CustomObjectMapper {

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

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

        return mapper;
    }
}
Mixin support

Quarkus automates the registration of Jackson’s Mixin support, via the io.quarkus.jackson.JacksonMixin annotation. This annotation can be placed on classes that are meant to be used as Jackson mixins while the classes they are meant to customize are defined as the value of the annotation.

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 javax.inject.Singleton;
import javax.json.bind.JsonbConfig;
import javax.json.bind.serializer.JsonbSerializer;

@Singleton
public class FooSerializerRegistrationCustomizer implements JsonbConfigCustomizer {

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

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

import io.quarkus.jsonb.JsonbConfigCustomizer;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Instance;
import javax.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と対話するための簡単なウェブページを追加してみましょう。Quarkusは、 META-INF/resources ディレクトリーの下にある静的リソースを自動的に提供します。 src/main/resources/META-INF/resources ディレクトリーに、この fruits.html ファイルの内容を含む fruits.html ファイルを追加します。

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

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

    CLI
    quarkus dev
    Maven
    ./mvnw quarkus:dev
    Gradle
    ./gradlew --console=plain quarkusDev
  • ブラウザで http://localhost:8080/fruits.html を開きます。

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

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

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

CLI
quarkus build --native
Maven
./mvnw package -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メソッドを分析することで、自動的にそのような処理を行ってくれます。

JAX-RSの世界では、 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 javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/legumes")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
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 ファイルを追加します。

Open a browser to http://localhost:8080/legumes.html, and you will see our list of legumes.

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

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

    CLI
    quarkus build --native
    Maven
    ./mvnw package -Dnative
    Gradle
    ./gradlew build -Dquarkus.package.type=native
  • ./target/rest-json-quickstart-1.0-SNAPSHOT-runner で実行します

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

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

As mentioned above, the issue is that Quarkus was not able to determine the Legume class will require some reflection by analyzing the REST endpoints. The JSON serialization library tries to get the list of fields of Legume and gets an empty list, so it does not serialize the fields' data.

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

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

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

import io.quarkus.runtime.annotations.RegisterForReflection;

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

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

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

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

    CLI
    quarkus build --native
    Maven
    ./mvnw package -Dnative
    Gradle
    ./gradlew build -Dquarkus.package.type=native
  • ./target/rest-json-quickstart-1.0-SNAPSHOT-runner で実行します

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

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

Reactiveになる

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

RESTEasy Reactiveは、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 を使ってアノテーションを付ける必要があります。