みんなのためのMicroProfile OpenAPI

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 の例を見てみましょう。messageto フィールドを持つ Greeting オブジェクトがあります。そして、greeting 用の GETPOSTDELETE エンドポイントを作成します。

通常の 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 {
    //...
}
  1. 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
  1. 自動生成された操作 ID

OpenAPI のバージョンを変更

API ゲートウェイによっては、動作するために特定の OpenAPI バージョンが必要な場合があります。SmallRye 拡張機能で生成されたスキーマドキュメントは 3.0.3 をバージョンとして生成されます。ですが、これらのバージョン間にはわずかな違いしかないので、3.0.03.0.13.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.xmlquarkus-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が表示されます。

https://quarkus.io/assets/images/posts/openapi-for-everyone/swagger-ui.png

まとめ

今回の記事では、Quarkus が MicroProfile OpenAPI 仕様を拡張し、エンドポイントのドキュメント化をより簡単にする方法を見てみました。また、それを使ってどのような Web フレームワークを文書化するかについても見てみました。

何か問題を見つけたり、提案があれば、SmallRye OpenAPI プロジェクトに移動して、そこで議論しましょう。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。