OpenAPI Generatorで生成したAPIクライアントをコンパイルせずに利用する

こんにちは、エキサイト株式会社の平石です。

今回は、OpenAPI Generatorで自動生成したAPIクライアントをコンパイルせずに利用する方法をご紹介します。

はじめに

OpenAPI Generatorと、Javaでの基本的な利用方法は以下のブログをご覧ください。

tech.excite.co.jp

以前のブログでは、自動生成したAPIクライアントをローカルリポジトリに保存する方法をご紹介しました。

tech.excite.co.jp

しかし、この方法ではサーバー環境上で実行する際には、Jarファイルをアプリケーションコードともにアップロードする必要があります。

解決策として、別途他のサーバーやAWS S3のようなものを用意して、そこにJarファイルをアップロードしライブラリの取得先として利用することが考えられます。
この方法も十分選択肢に入りますが、そもそものアップロード先の用意や、Jarファイルをアップロードする仕組みの構築がやや面倒です。

そこで、今回は生成した自動生成ファイルをそのまま利用してみたいと思います。

環境

今回は以下のような環境を利用します。

手順

といっても、非常にシンプルです。

以下の手順で行います。

  1. これまでの方法で一旦生成する
  2. 生成したファイルを実行するために必要な依存関係を確認する
  3. 2で確認した依存関係を自身で管理するbuild.gradleに追加する
  4. OpenAPI Generatorの設定を確認、適宜変更し再生成する
  5. 不要なファイルを削除する

各ステップを詳しく説明します。
なお、今回は最終的に自動生成ファイルを:api:client:sampleプロジェクトに配置します。

今回の記事におけるディレクトリ構成は以下のとおりです。

project_root
    ├ openapi
    │    ┗ build.gradle
    ├ api
    │  ┗ client
    │        ┗ sample  // この配下にAPIクライアントを作成
    ├ project1
    │    ┗ controller
    │        ┗ src/main/java/com/example/controller/SampleController.java
    ┗ build.gradle  // openapiプロジェクト以外の依存関係等を記述

1. これまでの方法で一旦生成する

こちらのブログでご紹介した内容に沿って、APIクライアントを生成します。

tech.excite.co.jp

具体的には、「実際に生成してみる」節まで実行します。

2. 生成したファイルを実行するために必要な依存関係を確認する

APIクライアント生成を実行すると、openapi/clientgenに多くのファイルが生成されています。
この中から、build.gradleMavenを利用している方はpom.xml)の中の依存関係の部分を確認します。

上記記事の設定の場合には、以下のようになっています。

dependencies {
    implementation 'io.swagger:swagger-annotations:1.6.8'
    implementation "com.google.code.findbugs:jsr305:3.0.2"
    implementation 'com.squareup.okhttp3:okhttp:4.10.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
    implementation 'com.google.code.gson:gson:2.9.1'
    implementation 'io.gsonfire:gson-fire:1.9.0'
    implementation 'javax.ws.rs:jsr311-api:1.1.1'
    implementation 'javax.ws.rs:javax.ws.rs-api:2.1.1'
    implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
    implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
    implementation "jakarta.annotation:jakarta.annotation-api:$jakarta_annotation_version"
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
    testImplementation 'org.mockito:mockito-core:3.12.4'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
}

3. 依存関係を自身で管理するbuild.gradleに追加する

2で確認した依存関係を、APIクライアントを配置するプロジェクト(今回の場合、:api:client:sample)の設定をおこなっているbuild.gradleに追加します。

なお、これらの依存関係の中には、テストで必要な依存関係もあるので、必要ない場合には削除してください。

今回は以下のようになりました。

build.gradle

project(':api:client:sample') {
    dependencies {
        implementation "com.google.code.findbugs:jsr305:3.0.2"
        implementation 'com.squareup.okhttp3:okhttp:4.10.0'
        implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
        implementation 'com.google.code.gson:gson:2.9.1'
        implementation 'io.gsonfire:gson-fire:1.9.0'
        implementation 'javax.ws.rs:jsr311-api:1.1.1'
        implementation 'javax.ws.rs:javax.ws.rs-api:2.1.1'
        implementation 'org.openapitools:jackson-databind-nullable:0.2.6'
        implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
        implementation "jakarta.annotation:jakarta.annotation-api:1.3.5"
    }
}

4. OpenAPI Generatorの設定を確認、適宜変更し再生成する

OpenAPI Generatorの設定を改めて確認します。
上記記事では以下のように設定しました。

openApiGenerate {
    generatorName.set("java")
    inputSpec.set("${rootDir}/specs/test-api-docs.yaml")
    outputDir.set("${projectDir}/clientgen")
    apiPackage.set("com.example.clientgen.api")
    invokerPackage.set("com.example.clientgen.invoker")
    modelPackage.set("com.example.clientgen.model")
    configOptions.set([
         groupId: "com.example",
         artifactVersion: "0.0.1-SNAPSHOT",
    ])
}

今回は、生成したファイルを直接利用するため、build.gradlepom.xmlに反映されるgroupIdartifactVersionをあえて設定する必要はありません。

また、configOptions.set内でhideGenerationTimestamptrueに設定すると、生成ファイル内にタイムスタンプが含まれなくなります。 生成したファイルをGit管理する場合には、余計な差分が生じなくなるためおすすめです。

ここで、outputDir.set"$projectDir/api/client/sample"に設定することで、:api:client:sampleプロジェクト内にAPIクライアントを生成できます。

openapi/build.gradle

openApiGenerate {
    generatorName.set("java")
    inputSpec.set("${rootDir}/specs/test-api-docs.yaml")
    outputDir.set("${rootDir}/api/client/sample")
    apiPackage.set("com.example.clientgen.api")
    invokerPackage.set("com.example.clientgen.invoker")
    modelPackage.set("com.example.clientgen.model")
    configOptions.set([
         hideGenerationTimestamp: "true"
    ])
}

この設定でAPIクライアントを再生成します。

なお、${projectDir}/clientgen内に生成されたファイルを:api:client:sampleにコピーするようにしても構いません。

5. 不要なファイルを削除する

4で生成したファイル内で、srcディレクトリ内のコード以外は不要なため削除しても問題ありません。

実際に利用する際には、APIクライアントの自動生成とともに、src配下以外を自動で削除するようなGradleタスクを作成しておくと良いでしょう。

動作確認

では、コンパイルせずにJavaファイルのまま利用できるかどうか、動作確認をします。

APIを利用するプロジェクトの依存関係に、api:client:sampleプロジェクトを追加します。

build.gradle

project(':project1:controller') {
    bootJar {
        enabled = true
    }

    dependencies {
        // 略

        // 追加
        implementation project(':api:client:sample')
    }
}

APIを呼び出してみます。呼び出し先のAPIは以下のようになっています。
パラメータuserId11111111を渡すことでユーザー情報を取得できます。

@RestController
@RequestMapping("user")
public class UserController {
    @GetMapping
    public UserResponseDto getUser(
            @ModelAttribute @ParameterObject UserRequestDto requestDto
    ) {
        if (Objects.equals(requestDto.userId(), "11111111")) {
            return new UserResponseDto()
                    .setUserId("11111111")
                    .setUserName("白金 高輪")
                    .setEmail("takanawa.shirokane@example.com");
        }

        return new UserResponseDto()
                .setUserId("")
                .setUserName("")
                .setEmail("");
    }
}

呼び出し側は以下の通りです。
APIを呼び出し、取得したユーザー情報からユーザー名を返します。

project1/controller/src/main/java/com/example/controller/SampleController.java

@RestController
@RequestMapping("sample")
public class SampleController {
    @GetMapping
    public String sample() {
        final UserApi userApi = new UserApi();

        try {
            return userApi.getUser("11111111").getUserName();
        } catch (ApiException e) {
            throw new RuntimeException(e);
        }
    }
}

アプリケーションを起動して/sampleにアクセスすると、「白金 高輪」という文字列が返ってきます。

終わりに

今回は、OpenAPI Generatorで生成したJavaファイルをJarに変換せずに直接利用するための手順を紹介しました。
Jarファイルで管理するよりも差分もわかりやすいですし、今後はこちらの方法で運用していくのも良さそうです。

では、また次回。

参考文献