openAPIを使ってAndroidとSpringBoot3.x+kotlinで通信してみる

# openAPIとは？
Open API Spesification」の略で、REST APIの記述フォーマットを指します。REST APIとは、Webアプリケーション同士の情報のやり取りを安全に行うための規格のことです。

クライアントとサーバでそのopenAPI spec(仕様)に従たがったインタフェースでやりとりするので、インタフェースが食い違ってミスマッチになることがありません。

また、openAPI spec(仕様)から各種、色々な言語のクライアント側のスタブ、サーバ側のドライバを生成することができます。

使い方には以下の2通りがありますが、

## ①openAPI spec(仕様)から作る
イメージ的にはこのような図になります。openAPI spec(仕様)はOAS(OpenAPI Specification)に従って記述します。普通のテキストファイル(jsonまたはyaml)なので、エディタさえあれば作成可能ですが、OASの仕様に従った専用のツールがいくつかあります。(後述)
お互いの通信に関わる部分、クライアント側のソース、サーバ側のソースは仕様からプラグインを使って、自動生成します。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/c749047c-eccb-f03d-3b15-833479984f16.png">

## ②サーバ側の実装から作る
既にあるサーバ側の実装に仕様を記述します。今回の場合で、具体例を言うとサーバサイド側はSprintBootです。Controllerの\@PostMapping、\@GetMappingにアノテーションで仕様を記述します。
仕様はサーバ側のソースから、プラグインで生成します。その仕様からクライアント側の通信部分のソースを、またプラグインで自動生成します。
<img width="50%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/933408/e6dce428-890e-e071-608d-2670573d247c.png">

# openAPIを取り巻く環境
色々なライブラリ、ミドルウェアのバージョンアップに伴い、openAPIを取り巻く環境環境も変わってきています。
springBootを前提で説明しますが、過去は
* springBoot 2.x
* swagger UI
* swagger Editor
* Swagger Codegen

が鉄板の組み合わせで今でもネット上ではこれらの組み合わせの情報が多いです。

その歴史を紐解くと
* 最初はswaggerフレームワーク(ui、editor、codegen)がスタートラインでした。
* そのswaggerフレームワークの中から仕様の部分だけがopenAPIとして分離しました。
  * 2016年 別プロジェクトとして分離
  * 2017年 openAPI 仕様 3.0.0 リリース
  * 2017年 openAPI 仕様 3.0.1 リリース
  * 2018年 openAPI 仕様 3.0.2 リリース
  * 2020年 openAPI 仕様 3.0.3 リリース
  * 2021年 openAPI 仕様 3.1.0 リリース (Wikipediaから)
* 今は、仕様の部分はopenAPI、swaggerはその実装という分け方になっています。別れてからswaggerフレームワークのバージョンとopenAPIのバージョンは別々なので、ややこしいです。
* 2022年11月 springBoot 3.0 リリース

springBootは3.xがリリースされてもうしばらく経っており、今後はspringBoot 3.xが主流になると思われます。
springBoot 3.xになるとspringBoot 2.x時代のライブラリが対応していなくて、そのままでは使えません。

springBoot 2.xから3.xに変わったのに伴い
* SpringFoxは動作しなくなりました。Springdoc-openapiに変わりました。
* swagger codegen(今でもギリギリ使えるのかな？)はopenapi generator に置き換え。

一応、openapi generatorはkotlin-springBoot3のサーバサイドのソースを自動生成できるらしい。(試していないが)

今回は、サーバサイド側は、springBoot3+kotlin、それにControllerに仕様をアノテーションで追記する方式、クライアント側はAndroid+kotlinを前提に、上記の図の②の方式で説明します。

# SpringBoot 3.xのサーバサイド側の設定
まずは、build.gradle.ktsにプラグイン、ライブラリ、タスクを追加します
```kotlin:build.gradle.kts
plugins {
    kotlin("jvm") version "1.9.0"
    application
    id("org.springframework.boot") version "3.0.5"
    id("io.spring.dependency-management") version "1.1.0"
    id("org.springdoc.openapi-gradle-plugin") version "1.7.0" // ★(1)
    id("org.jetbrains.kotlin.plugin.spring") version "1.7.22"
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-web-services")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    // Open-API
    implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0") // ★(2)
}

// ★(3)
openApi {
    apiDocsUrl.set("http://localhost:8000/v3/api-docs")
    outputDir.set(file("apiDocs"))
    outputFileName.set("MyApiServer.json")
    waitTimeInSeconds.set(10)
    customBootRun {
         workingDir.set(buildDir)
    }
}
```
* ★(1)がspringdoc openapi gradle pluginです。
* ★(2)がSpringFoxに変わるSpringdoc-openapiです。
* ★(3)がspringControllerのアノテーションからopenAPI spec(仕様)を生成するタスクです。gradleのgenerateOpenApiDocsタスクを起動するとサーバが起動してopenAPI spec(仕様)を{projectRoot}/apiDocs/MyApiServer.jsonに保存します。

:::note warn
ここで、ひとつ注意点なんですが、gradleのgenerateOpenApiDocsタスクを起動を起動してもOpenAPIのインタフェースのjsonが更新されない時があります。原因は不明です。うまくいくときといかないときがあります。
うまくいく場合はspringBootのサーバが起動され、多分gradleタスクの中でHTTPで取得してjsonに保存しているのだと思いますが、うまく行かない場合はspringBootのサーバ起動してません。
**うまく更新されない場合は、MyApiServer.jsonを一旦削除すると更新されます。**

もしくは gradleのオプション --rerun-tasks を付けると作成されます。

**gradle generateOpenApiDocs  --rerun-tasks**
:::

generateOpenApiDocsはデフォルトではjsonでopenAPI spec(仕様)を生成しますが、yamlにしたければ、
```kotlin:build.gradle.kts
openApi {
    apiDocsUrl.set("http://localhost:8000/v3/api-docs.yaml")
    outputDir.set(file("apiDocs"))
    outputFileName.set("MyApiServer.yaml")
    waitTimeInSeconds.set(10)
    customBootRun {
         workingDir.set(buildDir)
    }
}
```
にします。

springBootのControllerに付けるアノテーションは
[Swagger 2.X Annotations](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Annotations)
を参考に付けます。SpringFoxからSpringdoc-openapiに変わったのに伴い、アノテーションの書き方も変わっているので注意が必要です。
| SpringFox | Springdoc-openapi |
|:-|:-|
| \@Api  | \@Tag  |
| \@ApiOperation  | \@Operation  |
| \@ApiResponse  | \@ApiResponse  |
| \@PostMapping  | \@RequestMapping  |
| \@ApiParam  |  \@Parameter |


★(2)でSpringdoc-openapiが組み込まれているので、springBootのmainを起動すると、今までのswagger-uiがブラウザで開いて見ることができます
http://{ホスト}:{ポート}/swagger-ui/index.html

Springdoc-openapiのドキュメントは
[springdoc-openapi v2.2.0](https://springdoc.org/)
を参照してください。

Springdoc-openapiは従来のswagger-uiの機能も内包しています。これらの動作を変更する場合は、application.prpperties、application.ymlに記述します。
詳細は上記のSpringdoc-openapiのドキュメントの
[5.1. springdoc-openapi core properties](https://springdoc.org/#springdoc-openapi-core-properties)
[5.2. swagger-ui properties](https://springdoc.org/#swagger-ui-properties)
を参照してください。

OpenAPI Generator Gradle Pluginのドキュメントは以下を参照してください。
[OpenAPI Generator Gradle Plugin](https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator-gradle-plugin/README.adoc)

# openAPI spec(仕様)からRetrofit2のkotlinクライアントを生成する
さて、springBoot3のサーバサイド側でControllerのアノテーションの仕様から生成したopenAPI spec(仕様)のファイルをAndroid側にコピーして持ってきます。

{プロジェクトルート}apiDocs/MyApiServer.json

に置きます。openAPI spec(仕様)からRetrofit2のクライアントノソースコードを自動生成してみます。
ます、build.gradle.ktsです
```kotlin:build.gradle.kts
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("org.openapi.generator") version "6.5.0"  //★(1)
}

dependencies {

    implementation("androidx.core:core-ktx:1.10.1")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.9.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")

    compileOnly("io.swagger.core.v3:swagger-annotations:2.2.7") //★(2) Swagger Annotations
    compileOnly("io.swagger.core.v3:swagger-models:2.2.7") //★(2) Swagger Models
    compileOnly("jakarta.annotation:jakarta.annotation-api:2.1.1") //★(2) Jakarta Annotations API

    val lifecycleVersion = "2.6.1"
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${lifecycleVersion}")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:${lifecycleVersion}")
    implementation("androidx.lifecycle:lifecycle-common-java8:${lifecycleVersion}")
    implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")

    //★(3)
    val retrofitVersion = "2.9.0" 
    implementation("com.squareup.moshi:moshi-kotlin:1.15.0")
    implementation("com.squareup.moshi:moshi-adapters:1.13.0")
    implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
    implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
    implementation("com.squareup.retrofit2:converter-moshi:$retrofitVersion")
    implementation("com.squareup.retrofit2:converter-scalars:$retrofitVersion")
}
//★(4)
task<GenerateTask>("generateApiServer") {
    generatorName.set("kotlin")
    inputSpec.set("$rootDir//apiDocs/MyApiServer.json")
    outputDir.set("$buildDir/generated")
    apiPackage.set("jp.co.mycompany.myserver.api")
    invokerPackage.set("jp.co.mycompany.myserver.invoker")
    modelPackage.set("jp.co.mycompany.myserver.model")
    packageName.set("jp.co.mycompany.myserver")
    library.set("jvm-retrofit2")
    configOptions.set(
        mapOf(
            "useCoroutines" to "true",
            "enumPropertyNaming" to "original")
    )
}
//★(5)
kotlin.sourceSets.main {
    kotlin.srcDir("$buildDir/generated/src/main/kotlin")
}
```
*  ★(1)がopenapi-generator pluginです。
*  ★(2)は生成したソースコード内で利用されているライブラリを記述します。
*  ★(3)はRetrofit2のクライアントのソースコードを自動生成するので、Retrofit2に必要なライブラリの依存関係を追加します。
*  ★(4)がRetrofit2のクライアントのソースコードを自動生成するタスクです。自動生成したコードはopenAPI spec(仕様)から一方向で生成されるので、このソースを修正してもopenAPI spec(仕様)が変わってまた自動生成したら上書きされます。なので、buildディレクトリに生成し、VCSの管理も対象外にします。
*  ★(5)は自動生成したソースコードをソースパスに追加して、他のソースから参照可能にしています。

gradleのgenerateApiServerタスクを実行すると、

{プロジェクトルート}/app/build/generated

以下にソースが生成されます。生成されたソースはRetrofitのインタフェースとRetrofit.Builder()を呼び出しているクラスまでです。非同期の部分はcoroutineで自前で実装が必要です。
Activity、Fragmentの中で、
```kotlin
lifecycleScope.launch(Dispatchers.IO) {
    val client = ApiClient(baseUrl = "http://192.168.0.1:8080")
    val service = client.createService(MyControllerApi::class.java)
    val request = LoginRequest("00001", "testuser", "testpass")
    val call = service.doLogin(request)
    val response = call.execute()
    Log.d("OpenAPI", response.body().toString())
}
```
と、言った具合に呼び出します

openapi generatorについては以下のURLを参照してください。
[OpenAPI Generatorトップ](https://openapi-generator.tech/)
[generators 生成可能なソースの一覧](https://openapi-generator.tech/docs/generators)
[クライアント側kotlinの生成](https://openapi-generator.tech/docs/generators/kotlin)
[サーバ側kotlin-springの生成](https://openapi-generator.tech/docs/generators/kotlin-spring)

# openAPI spec(仕様)を編集するツール
openAPI spec(仕様)はテキスト(jsonまたは、yaml)なのでテキストエディタで編集可能ですが、折角なので専用のツールを使った方が効率がいいです。
* [Swagger editor](https://editor.swagger.io/)
* [StopLight](https://stoplight.io/)
* [ApiDoc](https://apidog.com/jp/)

Swagger editorは昔からのおなじみです。一応openAPI 3.xにも対応しています。
昔は、StopLightは使い勝手が良かったのですが、最近は何故か、Webの方に登録を強制されるので使いづらいです。ApiDocの方が日本語に対応しているのと、クライアント、サーバ側のソースをここから生成できるので、ApiDocの方が使い勝手がよさそうです。