uokadaの見逃し三振は嫌いです

ここで述べられていることは私の個人的な意見に基づくものであり、私が所属する組織には一切の関係はありません。

quarkus-smallrye-openapi とInstancioを使って手軽にOpenAPI.yamlとスタブサーバーを作る

ja.quarkus.io

www.instancio.org

quarkus-smallrye-openapi はJavaのクラスからOpenAPI のYAML/JSONを生成出来るQuarkusの拡張で、InstancioはJavaのクラスから手早くダミーのオブジェクトを生成するライブラリです。

自分はこれらを2つの目的で使用しています。

1つ目はOpenAPI YAMLの生成・提供です。OpenAPI YAMLを直接書いても良いのかもしれませんが、修正作業が入ったときに手作業だと修正漏れなどのミスが発生する可能性が高いです。Javaファイルの修正の場合IDEに修正を任せらることやCompilerでエラーの検出が可能となります。

2つ目はスタブサーバーの提供です。ダミーデータの生成をするライブラリはdatafakerなどいくつかありますが、自分はInstancioが一番手軽に利用できるライブラリだと思います。 よりリアルなスタブサーバーを作成したい場合には、datafakerを使ってリアルなデータを生成して使っています。

次からは、どうやって今回の目的のquarkusアプリケーションを生成するか手順を紹介します。

まず、quarkusコマンド*1を使ってアプリケーションを生成します。

# quarkus create app --java <java version> --maven -P <quarkus version> --wrapper --extension <Quarkus extensionのリスト(カンマ区切り)> -o <出力先ディレクトリ> <Javaアプリケーション>
$ quarkus create app --java 17 --maven -P 3.11.3 --wrapper --extension io.quarkus:quarkus-smallrye-openapi,rest-jackson -o . io.github.yuokada.sample:quarkus-openapi-app
-----------
selected extensions:
- io.quarkus:quarkus-rest-jackson
- io.quarkus:quarkus-smallrye-openapi


applying codestarts...
📚 java
🔨 maven
📦 quarkus
📝 config-properties
🔧 tooling-dockerfiles
🔧 tooling-maven-wrapper
🚀 rest-codestart

-----------
[SUCCESS] ✅  quarkus project has been successfully generated in:
--> /path/to/quarkus-openapi-app
-----------
Navigate into this directory and get started: quarkus dev

次に、ダミーデータを生成するためにpom.xmlに次のコードを追加してinstancio-coreを導入します。

    <dependency>
      <groupId>org.instancio</groupId>
      <artifactId>instancio-core</artifactId>
      <version>4.8.0</version>
    </dependency>

そして、APIの実装をInterfaceを使って定義していきます。Interfaceを作成せずClassに直接アノテーションを付与してもOpenAPI yamlの生成は出来ますが、OpenAPIの複数のアノテーションを付与すると見通しが悪くなるので自分はInterfaceとClassに分けるスタイルを採用しています。

メソッドに@GET, @Path, @APIResponses などのAPIエンドポイントの定義をアノテーションで付与していきます。*2

必須なアノテーションとしては @(GET|POST|PUT|DELETE), @Path をぐらいを定義していればスタブサーバーの生成には十分です。あとは、必要に応じてクエリパラメータ・パスパラメータなどを定義します。

@Produces(MediaType.APPLICATION_JSON)
public interface SampleApiIf {

    @GET
    @Path("/")
    @Operation(
        summary = "Return a list of players",
        description = "Return a list of players"
    )
    @APIResponses({
        @APIResponse(
            responseCode = "200",
            description = "Returns a list of players",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(type = SchemaType.ARRAY, implementation = Player.class)
            )
        ),
    })
    Response getPlayers();

    @GET
    @Path("/{id}")
    @Operation(
        summary = "Return the detail of a player",
        description = "Return the detail of a player"
    )
    @APIResponses({
        @APIResponse(
            responseCode = "200",
            description = "Returns the detail of a player",
            content = @Content(
                mediaType = "application/json",
                schema = @Schema(type = SchemaType.OBJECT, implementation = Player.class)
            )
        ),
    })
    Response getPlayer(@PathParam("id") Long userId);
}

Interfaceの定義が完了したら次はその実装を作ります。今回はInstancioを使ってダミーデータを生成しそれをAPIのレスポンスとします。

Instancioでは完全にランダムな値を生成する以外にもset()で値を指定したり、generate()で範囲を決めたランダムな値を付与することが出来ます。

@Path("/v1/api/stub/players")
@Tag(name = "Demo App stub API", description = "Demo App Common APIs for development")
@Deprecated
public class StubApiImpl implements SampleApiIf {

    @Override
    public Response getPlayers() {
        List<Player> players = Instancio.ofList(Player.class).size(10)
            .generate(field(Player.class, "age"), gen -> gen.ints().range(15, 85))
            .create();
        return Response.ok(players).build();
    }

    @Override
    public Response getPlayer(Long userId) {
        Player player = Instancio.of(Player.class)
            .set(field("userId"), userId)
            .generate(field("age"), gen -> gen.ints().range(15, 85))
            .create();
        return Response.ok(player).build();
    }
}

ここまで実装が出来たらmavenからquarkus サーバーを立ち上げます。デフォルトだと http://localhost:8080 でサーバーが立ち上がります。 また、http://localhost:8080/q/swagger-ui/#/ でSwagger UIが利用できるので実装したAPIに手軽にリクエストを投げて検証出来ます。

$ ./mvnw quarkus:dev

openapi.yamlの出力先はquarkus.smallrye-openapi.store-schema-directoryで変更することが出来ます。次の例だとプロジェクト直下にopenapi-definitionが作成されてそこに出力されます。

# OpenApi
quarkus.smallrye-openapi.enable=true
quarkus.smallrye-openapi.store-schema-directory=openapi-definition

実装に迷った場合はこちらのリポジトリが参考になります。 github.com

まとめです。

quarkus-smallrye-openapi を使えばOpenAPIの仕様をあまり知らなくてもOpenAPI yamlの生成が簡単に出来ることが伝わったかと思います。 自分も昔OpenAPI yamlを手書きしていましたが、仕様をある程度知らないと書くのも難しいし修正も大変だった経験があるのでJavaのコードから自動的に生成出来るというのは作成・修正するハードルが下がるので良い手法だなと思いました。

今回のコードのリポジトリはこちらです。 github.com

*1: インストールはこちらから。ja.quarkus.io

*2:詳しい仕様はこちらを参照してください。 download.eclipse.org