概要
初めて触るフレームワークなので調べたことをまとまりなく書いていく。
諸元
- Quarkus: 3.15.1
- Java: 21
リファレンス
- 公式ガイド: https://ja.quarkus.io/guides/
- RedHatのSAの方の記事: https://rheb.hatenablog.com/entry/microprofile_openapi
REST API実装編
エンドポイントに共通のパスを付与したい
package org.acme.rest; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; @ApplicationPath("/api") public static class MyApplication extends Application {}
みたいにやるとすべてのエンドポイントの頭に /api
をつけられる。
リクエストパラメタのコンテナクラスにRecordクラスを使いたい(けどできない)
複数のリクエストパラメタをグループするカスタムクラスを実装することができる。これはBeanParamを利用している。
@Path("/cheeses/{type}") public class Endpoint { public static class Parameters { @RestPath String type; // ...snip... @RestForm String smell; } @POST public String allParams(@BeanParam Parameters parameters) { // <- ココ! return parameters.type + "/" + parameters.variant + "/" + parameters.age + "/" + parameters.level + "/" + parameters.secretHandshake + "/" + parameters.smell; } }
ref: カスタムクラスでパラメータをグループ化
このカスタムクラスをRecordクラスで実装したくなるが、Java Beanの規約に準拠していないため残念ながら使えない様子。
- https://github.com/jakartaee/rest/issues/913
- https://blogs.oracle.com/javamagazine/post/diving-into-java-records-serialization-marshaling-and-bean-state-validation
パスパラメタやクエリパラメタの型を正規表現で表したい
@Path("{name}/{age:\\d+}") @GET public String personalisedHello(String name, int age) { return "Hello " + name + " is your age really " + age + "?"; } @GET public String genericHello() { return "Hello stranger"; }
{age:\\d+}
などとするとパラメタ名を正規表現でパターンマッチングかつint等に変換した状態でメソッドに渡すことができる。
マッチしない場合はフォールバックできるメソッドがあればそちらにいく。
オーバーロードのような動作も実現できるが、意図しない動きに繋がりそうなのでやらないほうがいいと思った。
@Path("{name}/{age:\\d+}") @GET public String personalizedHello(String name, int age) { return String.format("%s is your age really %d?", name, age); } @Path("{name}/{comeFrom}") @GET public String personalizedHello(String name, String comeFrom) { return String.format("Are %s from %s?", name, comeFrom); }
レスポンスをJsonで返したい。
quarkus-rest-jackson
のextensionを入れるとJSONで自動的に返すようになる。
ref: https://quarkus.io/guides/rest#json-serialisation
任意のステータスコードとレスポンスで返したい
WebApplicationException
(これはJakartaの資産)を継承した各種例外をthrowすると対応するステータスコードになる。
@GET public String findCheese(String cheese) { if(cheese == null) // send a 400 throw new BadRequestException(); if(!cheese.equals("camembert")) // send a 404 throw new NotFoundException("Unknown cheese: " + cheese); return "Camembert is a very nice cheese"; }
ref: - 例外のマッピング - java - WebApplicationException vs Response - Stack Overflow
または、メソッドの型を Response
にし、以下のような形で任意のHTTPステータスコードと任意のオブジェクトを返せるが公式ガイドに寄れば↑の実装が最善とのこと。
@Path("{id}") @GET public Response getPerson(Long id) { var p = new Person(id, "foo", "bar", "Brick Lane"); return Response.status(Response.Status.NOT_FOUND).entity(p).build(); }
(OpenAPI定義にエラーを反映させる方法は?)
Servlet Filter的なものがほしい
@ServerRequestFilter
アノテーションおよび @ServerResponseFilter
アノテーションで実装できる。
ref: リクエストまたはレスポンスフィルター
OpenAPI編
OpenAPI定義を生成したい
quarkus-smallrye-openapi
拡張を導入することで、 /q/openapi?format=json
からOpenAPI定義を手に入れることができる。
ref: https://ja.quarkus.io/guides/openapi-swaggerui#expose-openapi-specifications
info
や tags
などを追加で定義したい場合は2つの方法があり、1つは @OpenAPIDefinition
アノテーションを利用する方法、もう1つは設定ファイル application.{properties|yaml}
を利用する方法。
どっちがいいかは好みか? ついでにSwagger-uiも生える。 http://localhost:8080/q/swagger-ui
ref: アプリケーションレベルのOpenAPIアノテーションの提供
OpenAPI定義から生成されるメソッド名をいい感じにして欲しい
OpenAPI定義から生成される実装は、OperationID
を元にメソッド名を決める(ことが仕様?)。メソッド毎に @OperationID
アノテーションを利用することもできるが、設定ファイルに以下のように書くとメソッド名がそのまま利用できる。
mp.openapi.extensions.smallrye.operationIdStrategy=METHOD
この辺のOpenAPIの実装は SmallRye というプロジェクトの資産を流用しているっぽいので深掘る場合はそちらか。
ref:
自動テスト系(T.B.D.)
quarkus-junit5
拡張によるテストフレームワークを導入出来るっぽい。BDD味があるのと、QuarkusがWAFであるためエンドポイント単位のテストがスコープっぽい。
その他
構造化ログを出力したい
DataDogやCloud Loggingにログを出すなら、構造化ログにすることが望ましい。
quarkus-logging-json
拡張を導入すると自動的に構造化ログが出力される。細かい設定はガイド記事を見てほしい。