JSON RESTサービスの実装
JSON は今やマイクロサービス間の 共通言語 となっています。
このガイドでは、REST サービスが JSON ペイロードを利用および生成する方法を見ていきます。
RESTクライアント (JSONのサポートを含む)が必要な場合は、別のガイドがあります。 |
QuarkusでJSON RESTサービスを記述する方法を紹介します。 Quarkus REST(旧RESTEasy Reactive)のより詳細なガイドは こちら をご覧ください。 |
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOME
が適切に設定されていること -
Apache Maven 3.9.9
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
アーキテクチャ
このガイドで開発するアプリケーションは非常にシンプルです: ユーザーはフォームを使用してリストに要素を追加することができ、リストが更新されます。
ブラウザとサーバー間の情報はすべて JSON 形式になっています。
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。ただし、完成した例にそのまま進んでも構いません。
Gitレポジトリをクローンするか git clone https://github.com/quarkusio/quarkus-quickstarts.git
、 アーカイブ をダウンロードします。
ソリューションは rest-json-quickstart
ディレクトリ にあります。
Maven プロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します:
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\
を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-D
パラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=rest-json-quickstart"
このコマンドは、Quarkus REST/Jakarta RESTおよび Jackson エクステンションをインポートする新しいプロジェクトを生成し、 特に以下の依存関係を追加します:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-jackson")
ユーザーエクスペリエンスを向上させるために、Quarkusは3つのJackson Java 8モジュール を登録しているので、手動で登録する必要はありません。 |
Quarkusは JSON-B もサポートしているため、JacksonよりもJSON-Bを好む場合は、代わりにQuarkus REST JSON-Bエクステンションに依存するプロジェクトを作成できます:
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\
を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-D
パラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=rest-json-quickstart"
このコマンドは、Quarkus REST/Jakarta RESTおよび JSON-B エクステンションをインポートする新しいプロジェクトを生成し、 特に以下の依存関係を追加します:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jsonb</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-jsonb")
If you use JSON-B and JSON-P, make sure you don’t use the shortcut methods offered by At the moment, any single call to these methods will initialize a new You can import it as a static import to simplify your code:
|
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アノテーションを使用してエンドポイントを定義するだけです。
|
JSONサポートの設定
Jackson
Quarkusでは、CDI経由で取得した(そしてQuarkusのエクステンションによって消費される)デフォルトのJackson ObjectMapper
は、未知のプロパティーを無視するように設定されています( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES
機能を無効にすることで)。
application.properties
で quarkus.jackson.fail-on-unknown-properties=true
を設定するか、 クラスごとに @JsonIgnoreProperties(ignoreUnknown = false)
を設定することで、Jackson のデフォルトの動作を復元することができます。
さらに、 ObjectMapper
、日付と時刻をISO-8601でフォーマットするように設定されています( SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
の機能を無効にすることで)。
application.properties
で quarkus.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;
}
}
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
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
実行は簡単で、 ./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
には、いくつかの良い特典があります:
-
メソッドで何が起こるかによって異なるエンティティータイプを返すことができます (例えば
Legume
やError
)。 -
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.native.enabled=true
-
./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.native.enabled=true
-
./target/rest-json-quickstart-1.0-SNAPSHOT-runner
で実行します -
ブラウザで http://localhost:8080/legumes.html を開きます
今回はマメ科の一覧が表示されました。
Reactiveになる
非同期処理を処理するために リアクティブ型 を返すことができます。Quarkusでは、リアクティブで非同期なコードを書くために Mutiny の使用を推奨しています。
Quarkus RESTは当然Mutinyと統合されています。
そして、エンドポイントは Uni
や Multi
のインスタンスを返すことができます:
@GET
@Path("/{name}")
public Uni<Fruit> getOne(String name) {
return findByName(name);
}
@GET
public Multi<Fruit> getAll() {
return findAll();
}
単一の結果がある場合は Uni
を使用します。 Multi
は、非同期的に放出される可能性のある複数の項目がある場合に使用します。
Uni
と Response
を使用して、非同期 HTTP レスポンスを返すことができます: Uni<Response>
.
Mutinyについての詳細は、 Mutiny - 直感的なリアクティブプログラミングライブラリ に記載されています。