What's?
OpenAPIドキュメントを書こうと思った時に、GUIツールを探したりしているのですがなかなか求めるものが見つからないので、ソースコードから生成した方がやりやすいのかなと思うようになりました。
自分はJavaを使うことが多いので、MicroProfileに含まれているMicroProfile OpenAPIとOpen LibertyのMicroProfile OpenAPIフィーチャーを使うのがよいのかなと思いました。
目的がOpenAPIドキュメントの作成に特化する場合は、クラスやメソッドなどの定義だけ書けばよく、中身の実装まではする必要はないですし。
Open LibertyのMicroProfile OpenAPIフィーチャー
Open LibertyのMicroProfile OpenAPIフィーチャーに関するページはこちらです。
このフィーチャーを有効にすると、Jakarta RESTful Web Services(JAX-RS)のApplication
やエンドポイントの内容からOpenAPIドキュメントを生成してくれます。
また、MicroProfile OpenAPIのアノテーションを使用することで、OpenAPIドキュメントとして必要な情報を付与することもできます。
Swagger UIも付属しているので確認もできますし、Liberty Maven Pluginのdev
ゴールを使用するとソースコードを編集すればすぐに反映されるので確認も簡単です。
こちらを使って、OpenAPIドキュメントを作成したいと思います。
ちなみに、現在のアプリケーションサーバに含まれているMicroProfile OpenAPIのバージョンは3.1で、これはOpenAPI 3.0.xに対応しています。
OpenAPI 3.1.0に対応するのは、次バージョンであるMicroProfile OpenAPI 4.0です。
環境
今回の環境はこちらです。
$ java --version
openjdk 21.0.4 2024-07-16
OpenJDK Runtime Environment (build 21.0.4+7-Ubuntu-1ubuntu222.04)
OpenJDK 64-Bit Server VM (build 21.0.4+7-Ubuntu-1ubuntu222.04, mixed mode, sharing)
$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: /home/charon/.sdkman/candidates/maven/current
Java version: 21.0.4, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.15.0-124-generic", arch: "amd64", family: "unix"
サンプルコード
簡単なサンプルコードを作成。
<?xml version="1.0" encoding="UTF-8" ?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>mp-openapi-editor</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-api</artifactId>
<version>10.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile</groupId>
<artifactId>microprofile</artifactId>
<version>6.1</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>ROOT</finalName>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.4.0</version>
</plugin>
<plugin>
<groupId>io.openliberty.tools</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>3.11.1</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>io.openliberty.tools</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<configuration>
<runtimeArtifact>
<groupId>io.openliberty</groupId>
<artifactId>openliberty-kernel</artifactId>
<version>24.0.0.10</version>
<type>zip</type>
</runtimeArtifact>
</configuration>
</plugin>
</plugins>
</build>
</project>
Open Libertyの設定。少なくともrestfulWS-3.1
、mpOpenAPI-3.1
のフィーチャーは必要です。面倒だったらコンビニエンスフィーチャーを指定してもよいでしょう。
<?xml version="1.0" encoding="UTF-8"?>
<server description="new server">
<!-- Enable features -->
<featureManager>
<feature>restfulWS-3.1</feature>
<feature>jsonb-3.0</feature>
<feature>mpOpenAPI-3.1</feature>
</featureManager>
<!-- This template enables security. To get the full use of all the capabilities, a keystore and user registry are required. -->
<!-- For the keystore, default keys are generated and stored in a keystore. To provide the keystore password, generate an
encoded password using bin/securityUtility encode and add it below in the password attribute of the keyStore element.
Then uncomment the keyStore element. -->
<!--
<keyStore password=""/>
-->
<!--For a user registry configuration, configure your user registry. For example, configure a basic user registry using the
basicRegistry element. Specify your own user name below in the name attribute of the user element. For the password,
generate an encoded password using bin/securityUtility encode and add it in the password attribute of the user element.
Then uncomment the user element. -->
<basicRegistry id="basic" realm="BasicRealm">
<!--
<user name="yourUserName" password="" />
-->
</basicRegistry>
<!-- To access this server from a remote client add a host attribute to the following element, e.g. host="*" -->
<httpEndpoint id="defaultHttpEndpoint"
host="0.0.0.0"
httpPort="9080"
httpsPort="9443" />
<!-- Automatically expand WAR files and EAR files -->
<applicationManager autoExpand="true"/>
<!-- Configures the application on a specified context root -->
<webApplication contextRoot="/" location="ROOT.war" />
<!-- Default SSL configuration enables trust for default certificates from the Java runtime -->
<ssl id="defaultSSLConfig" trustDefaultCerts="true" />
</server>
アプリケーションのコンテキストパスは/
、WARファイル名はROOT
にしていて、どのモードで起動してもコンテキストパスが/
にしています。
ソースコード。
package com.example.rest;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import org.eclipse.microprofile.openapi.annotations.OpenAPIDefinition;
import org.eclipse.microprofile.openapi.annotations.info.Info;
import org.eclipse.microprofile.openapi.annotations.servers.Server;
@ApplicationPath("/")
@OpenAPIDefinition(
info = @Info(
title = "OpenAPI Editor",
version = "0.0.1"
),
servers = @Server(
description = "OpenAPI Editor description",
url = "http://localhost:9080"
)
)
public class RestApplication extends Application {
}
package com.example.rest.api;
import com.example.rest.model.PokemonRequest;
import com.example.rest.model.PokemonResponse;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@Path("/pokemons")
@Tag(name = "pokemon")
public class PokemonsResource {
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
@Operation(operationId = "findPokemonById", summary = "ポケモンを取得する", description = "IDを指定してポケモンを取得する")
@APIResponses({
@APIResponse(responseCode = "200", description = "指定されたポケモンを取得できた場合"),
@APIResponse(responseCode = "404", description = "指定されたポケモンが見つからなかった場合")
})
public PokemonResponse findById(@PathParam("id") @Parameter(description = "ID") String id) {
return null;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Operation(operationId = "createPokemon", summary = "ポケモンを作成する", description = "ポケモンを作成する")
@APIResponses({
@APIResponse(responseCode = "201", description = "ポケモンを作成できた場合"),
@APIResponse(responseCode = "400", description = "リクエストが不正な場合")
})
public PokemonResponse create(PokemonRequest pokemon) {
return null;
}
}
package com.example.rest.model;
import jakarta.validation.constraints.NotEmpty;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Schema(description = "ポケモンリクエスト")
public record PokemonRequest(
@NotEmpty
@Schema(description = "名前")
String name
) {
}
package com.example.rest.model;
import jakarta.validation.constraints.NotEmpty;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Schema(description = "ポケモンレスポンス")
public record PokemonResponse(
@NotEmpty
@Schema(description = "名前")
String name
) {
}
あとは開発モードで起動しておきます。
$ mvn compile liberty:dev
生成されたOpenAPIドキュメントを確認する
生成されたOpenAPIドキュメントの確認。
$ curl localhost:9080/openapi
今回の例だと、こんな感じになりました。
openapi: 3.0.3
info:
title: OpenAPI Editor
version: 0.0.1
servers:
- url: http://localhost:9080
description: OpenAPI Editor description
tags:
- name: pokemon
paths:
/pokemons:
post:
tags:
- pokemon
summary: ポケモンを作成する
description: ポケモンを作成する
operationId: createPokemon
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PokemonRequest'
responses:
"201":
description: ポケモンを作成できた場合
"400":
description: リクエストが不正な場合
/pokemons/{id}:
get:
tags:
- pokemon
summary: ポケモンを取得する
description: IDを指定してポケモンを取得する
operationId: findPokemonById
parameters:
- name: id
in: path
description: ID
required: true
schema:
type: string
responses:
"200":
description: 指定されたポケモンを取得できた場合
content:
application/json:
schema:
$ref: '#/components/schemas/PokemonResponse'
"404":
description: 指定されたポケモンが見つからなかった場合
components:
schemas:
PokemonRequest:
description: ポケモンリクエスト
required:
- name
type: object
properties:
name:
description: 名前
minLength: 1
type: string
PokemonResponse:
description: ポケモンレスポンス
required:
- name
type: object
properties:
name:
description: 名前
minLength: 1
type: string
Swagger UIで確認したい場合は、http://localhost:9080/openapi/ui/
にアクセスします。
Liberty Maven Pluginの開発モードで実行している場合は、ソースコードの変更がすぐに反映されるので便利です。