Red Hat で Solution Architect として Quarkus を担当している伊藤ちひろ(@chiroito)です。
この記事は、Quarkus.io のブログ記事、MicroProfile OpenAPI for everyone の翻訳記事です。
MicroProfile OpenAPI は、主に JAX-RS エンドポイントに OpenAPI を追加するために使用されます。このブログ記事では、Quarkus で使用されている場合に、SmallRye の実装がどのように拡張され、いくつかの追加機能と、より多くのウェブフレームワークをサポートしているかを見ていきます。
Quarkus を使用
サンプルコードはこちらにあります。code.quarkus.io を使ってプロジェクトを初期化できます - SmallRye OpenAPI 拡張機能を確実に含むことを確認してください。
JAX-RS
まずは Quarkus での基本的な JAX-RS の例を見てみましょう。message
と to
フィールドを持つ Greeting
オブジェクトがあります。そして、greeting 用の GET
、POST
、DELETE
エンドポイントを作成します。
通常の Quarkus のセットアップとは別に、JAX-RS エンドポイントを作成するためには、pom.xml
に次のように記述する必要があります。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-openapi</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy</artifactId> </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-jsonb</artifactId> </dependency>
Quarkus では、Application
クラスは必要ありません。Endpointクラスを追加すればいいだけです。
@Path("/jax-rs") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class JaxRsGreeting { @GET @Path("/hello") public Greeting helloJaxRs() { return new Greeting("Hello", "JAX-RS"); } @POST @Path("/hello") public Greeting newHelloJaxRs(Greeting greeting) { return greeting; } @DELETE @Path("/hello/{message}") public void deleteHelloJaxRs(@PathParam("message") String message) { // Here do the delete... } }
今のところ MicroProfile OpenAPI アノテーションはまだ追加していません。ですが、quarkus-smallrye-openapi
拡張機能を追加したので、すでに /openapi
の下に Schema ドキュメントが生成されています。
--- openapi: 3.0.3 info: title: Generated API version: "1.0" paths: /jax-rs/hello: get: responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: requestBody: content: application/json: schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /jax-rs/hello/{message}: delete: parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content components: schemas: Greeting: type: object properties: message: type: string to: type: string
詳細は quarkus.io/guides/rest-json を参照してください。
OpenAPI
MicroProfile OpenAPI を使用して、生成されたスキーマドキュメントに詳細な情報を追加できます。
config を使用したヘッダ情報
SmallRye に追加した機能の一つに、ヘッダ情報を追加する機能があります。MicroProfile の設定では、通常はアノテーションを使って Application
クラスに追加するものです。これは、Application
クラスを必要としないため Quarkus では便利です。そのため、application.properties
に以下を追加することで、ヘッダ情報を得られます。
mp.openapi.extensions.smallrye.info.title=OpenAPI for Everyone %dev.mp.openapi.extensions.smallrye.info.title=OpenAPI for Everyone (development) %test.mp.openapi.extensions.smallrye.info.title=OpenAPI for Everyone (test) mp.openapi.extensions.smallrye.info.version=1.0.0 mp.openapi.extensions.smallrye.info.description=Example on how to use OpenAPI everywhere mp.openapi.extensions.smallrye.info.contact.email=phillip.kruger@redhat.com mp.openapi.extensions.smallrye.info.contact.name=Phillip Kruger mp.openapi.extensions.smallrye.info.contact.url=https://www.phillip-kruger.com mp.openapi.extensions.smallrye.info.license.name=Apache 2.0 mp.openapi.extensions.smallrye.info.license.url=http://www.apache.org/licenses/LICENSE-2.0.html
次に、/openapi
の下にある生成されたスキーマドキュメントのヘッダを見てください。
--- openapi: 3.0.3 info: title: OpenAPI for Everyone (development) description: Example on how to use OpenAPI everywhere contact: name: Phillip Kruger url: https://www.phillip-kruger.com email: phillip.kruger@redhat.com license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 1.0.0 # Rest of the schema document...
操作に OpenAPI アノテーションを追加
MicroProfile OpenAPI のアノテーションを使用して、Tag
アノテーションなどのエンドポイントをさらに記述できます。
@Path("/jax-rs") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Tag(name = "JAX-RS Resource", description = "Basic Hello World using JAX-RS") ① public class JaxRsGreeting { //... }
- MicroProfile OpenAPI アノテーションの使用例
操作 ID を自動生成
スキーマドキュメントを使用してクライアントスタブを生成するツールもあります。これらは、スキーマドキュメント内の operationId
が必要です。これはクライアントスタブメソッドの名前に使用されます。SmallRye では、メソッド名(METHOD
)、クラスとメソッド名(CLASS_METHOD
)、パッケージとクラスとメソッド名(PACKAGE_CLASS_METHOD
)のいずれかを使用して自動生成するサポートを追加しました。これを行うには、application.properties
に次のように追加します。
mp.openapi.extensions.smallrye.operationIdStrategy=METHOD
これで、スキーマドキュメント内の各操作の operationId
が表示されます。
--- openapi: 3.0.3 # Header omitted... /jax-rs/hello: get: tags: - JAX-RS Resource operationId: helloJaxRs ① responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: tags: - JAX-RS Resource operationId: newHelloJaxRs ① requestBody: content: application/json: schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /jax-rs/hello/{message}: delete: tags: - JAX-RS Resource operationId: deleteHelloJaxRs ① parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content
- 自動生成された操作 ID
OpenAPI のバージョンを変更
API ゲートウェイによっては、動作するために特定の OpenAPI バージョンが必要な場合があります。SmallRye 拡張機能で生成されたスキーマドキュメントは 3.0.3
をバージョンとして生成されます。ですが、これらのバージョン間にはわずかな違いしかないので、3.0.0
、3.0.1
、3.0.2
に変更できます。これを application.properties
に追加することで実現できます。
mp.openapi.extensions.smallrye.openapi=3.0.2
これで生成されたバージョンになります。
--- openapi: 3.0.2 # Rest of the document...
詳細は quarkus.io/guides/openapi-swaggerui を参照してください。
Spring Web
最近、SmallRye OpenAPI に Spring Web のサポートが追加されました。これは、Quarkus で Spring Web を使用する際にデフォルトの OpenAPI ドキュメントが表示されるだけでなく、MicroProfile OpenAPI を使用して Spring Web のエンドポイントをさらに記述できることを意味します。
現在のアプリケーションに Spring Rest Controller を追加してみましょう。まず、これを pom.xml
に追加します。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-spring-web</artifactId> </dependency>
これまで見てきた JAX-RS と同じようなエンドポイントを Spring Web を使って作成できます。
@RestController @RequestMapping(value = "/spring", produces = MediaType.APPLICATION_JSON_VALUE) @Tag(name = "Spring Resource", description = "Basic Hello World using Spring") public class SpringGreeting { @GetMapping("/hello") public Greeting helloSpring() { return new Greeting("Hello", "Spring"); } @PostMapping("/hello") public Greeting newHelloSpring(@RequestBody Greeting greeting) { return greeting; } @DeleteMapping("/hello/{message}") public void deleteHelloSpring(@PathVariable(name = "message") String message) { // Here do the delete... } }
Spring の注釈はスキャンされ、スキーマドキュメントに追加されます。
--- openapi: 3.0.3 # Header omitted... /spring/hello: get: tags: - Spring Resource operationId: helloSpring responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: tags: - Spring Resource operationId: newHelloSpring requestBody: content: '_/_': schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /spring/hello/{message}: delete: tags: - Spring Resource operationId: deleteHelloSpring parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content
詳細は quarkus.io/guides/spring-web を参照してください。
Vert.x Reactive Routes
Quarkus では、Reactive Routes を使用して Vert.x エンドポイントを構築できます。Spring Web と同様に、エンドポイントは OpenAPI スキーマで利用可能になり、MicroProfile OpenAPI を使ってさらに記述できます。Quarkus で Vert.x Reactive Route を追加するには、pom.xml
に次のように記述する必要があります。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-vertx-web</artifactId> </dependency>
これでエンドポイントを作成できます。
@ApplicationScoped @RouteBase(path = "/vertx", produces = "application/json") @Tag(name = "Vert.x Resource", description = "Basic Hello World using Vert.x") public class VertxGreeting { @Route(path = "/hello", methods = HttpMethod.GET) public Greeting helloVertX() { return new Greeting("Hello", "Vert.x"); } @Route(path = "/hello", methods = HttpMethod.POST) public Greeting newHelloVertX(@Body Greeting greeting) { return greeting; } @Route(path = "/hello/:message", methods = HttpMethod.DELETE) public void deleteHelloVertX(@Param("message") String message) { // Here do the delete... } }
これで Vert.x Routes が OpenAPI で利用できるようになりました。
--- openapi: 3.0.3 # Header omitted... /vertx/hello: get: tags: - Vert.x Resource operationId: helloVertX responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: tags: - Vert.x Resource operationId: newHelloVertX requestBody: content: '_/_': schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /vertx/hello/{message}: delete: tags: - Vert.x Resource operationId: deleteHelloVertX parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content
詳細は quarkus.io/guides/reactive-routes を参照してください。
Panacheで生成されたエンドポイント
Quarkus では、Panache を使用してJAX-RS エンドポイントを生成できます。pom.xml
に quarkus-smallrye-openapi
拡張機能がある場合、これらの生成されたクラスもスキャンされ、OpenAPI スキーマドキュメントに追加されます。
詳細は quarkus.io/guides/rest-data-panache を参照してください。
他のWebフレームワーク
また、Schema ドキュメントのその部分を yaml
ファイルで提供することで、他のエンドポイントをドキュメントに追加できます。例えば、いくつかのメソッドを公開しているサーブレットを持っていて、それらをスキーマドキュメントに追加したいとしましょう。Servlet は一例であり、どんな Web フレームワークでも動作します。
そこでまず、Quarkus でサーブレットのサポートを追加するために、これを pom.xml
に追加します。
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-undertow</artifactId> </dependency>
これで、例えばこのような Servlet エンドポイントを作成できるようになりました。
@WebServlet("/other/hello/*") public class ServletGreeting extends HttpServlet { private static final Jsonb JSONB = JsonbBuilder.create(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException { response.setContentType("application/json"); Greeting greeting = new Greeting("Hello", "Other"); PrintWriter out = response.getWriter(); out.print(JSONB.toJson(greeting)); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("application/json"); Greeting greeting = JSONB.fromJson(request.getInputStream(), Greeting.class); PrintWriter out = response.getWriter(); out.print(JSONB.toJson(greeting)); } @Override protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Here do the delete... } }
ここで、これらのエンドポイントにマップする OpenAPI Schema ドキュメントが必要になります。src/main/resources/META-INF
にある openapi.yml
というファイルに追加する必要があります。
--- openapi: 3.0.3 tags: - name: Other Resource description: Basic Hello World using Something else paths: /other/hello: get: tags: - Other Resource operationId: helloOther responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: tags: - Other Resource operationId: newHelloOther requestBody: content: application/json: schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /other/hello/{message}: delete: tags: - Other Resource operationId: deleteHelloOther parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content
これは他のエンドポイントとマージされ、ドキュメント内のすべてのパスを公開します。そのため、最終的には /openapi
の出力は以下のようになります。
--- openapi: 3.0.2 info: title: OpenAPI for Everyone (development) description: Example on how to use OpenAPI everywhere contact: name: Phillip Kruger url: https://www.phillip-kruger.com email: phillip.kruger@redhat.com license: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0.html version: 1.0.0 tags: - name: Other Resource description: Basic Hello World using Something else - name: Spring Resource description: Basic Hello World using Spring - name: JAX-RS Resource description: Basic Hello World using JAX-RS - name: Vert.x Resource description: Basic Hello World using Vert.x paths: /other/hello: get: tags: - Other Resource operationId: helloOther responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: tags: - Other Resource operationId: newHelloOther requestBody: content: application/json: schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /other/hello/{message}: delete: tags: - Other Resource operationId: deleteHelloOther parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content /jax-rs/hello: get: tags: - JAX-RS Resource operationId: helloJaxRs responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: tags: - JAX-RS Resource operationId: newHelloJaxRs requestBody: content: application/json: schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /jax-rs/hello/{message}: delete: tags: - JAX-RS Resource operationId: deleteHelloJaxRs parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content /spring/hello: get: tags: - Spring Resource operationId: helloSpring responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: tags: - Spring Resource operationId: newHelloSpring requestBody: content: '_/_': schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /spring/hello/{message}: delete: tags: - Spring Resource operationId: deleteHelloSpring parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content /vertx/hello: get: tags: - Vert.x Resource operationId: helloVertX responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' post: tags: - Vert.x Resource operationId: newHelloVertX requestBody: content: '_/_': schema: $ref: '#/components/schemas/Greeting' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Greeting' /vertx/hello/{message}: delete: tags: - Vert.x Resource operationId: deleteHelloVertX parameters: - name: message in: path required: true schema: type: string responses: "204": description: No Content components: schemas: Greeting: type: object properties: message: type: string to: type: string
JAX-RS、Spring Web、Vert.x Reactive Routes、Servlet のリソースが含まれています。
Swagger UI
Quarkus では、デフォルトで Swagger UI が含まれており、localhost:8080/swagger-ui をブラウズすると、すべてのエンドポイントを含むUIが表示されます。
まとめ
今回の記事では、Quarkus が MicroProfile OpenAPI 仕様を拡張し、エンドポイントのドキュメント化をより簡単にする方法を見てみました。また、それを使ってどのような Web フレームワークを文書化するかについても見てみました。
何か問題を見つけたり、提案があれば、SmallRye OpenAPI プロジェクトに移動して、そこで議論しましょう。