openAPIとは?
Open API Spesification」の略で、REST APIの記述フォーマットを指します。REST APIとは、Webアプリケーション同士の情報のやり取りを安全に行うための規格のことです。
クライアントとサーバでそのopenAPI spec(仕様)に従たがったインタフェースでやりとりするので、インタフェースが食い違ってミスマッチになることがありません。
また、openAPI spec(仕様)から各種、色々な言語のクライアント側のスタブ、サーバ側のドライバを生成することができます。
使い方には以下の2通りがありますが、
①openAPI spec(仕様)から作る
イメージ的にはこのような図になります。openAPI spec(仕様)はOAS(OpenAPI Specification)に従って記述します。普通のテキストファイル(jsonまたはyaml)なので、エディタさえあれば作成可能ですが、OASの仕様に従った専用のツールがいくつかあります。(後述)
お互いの通信に関わる部分、クライアント側のソース、サーバ側のソースは仕様からプラグインを使って、自動生成します。
②サーバ側の実装から作る
既にあるサーバ側の実装に仕様を記述します。今回の場合で、具体例を言うとサーバサイド側はSprintBootです。Controllerの@PostMapping、@GetMappingにアノテーションで仕様を記述します。
仕様はサーバ側のソースから、プラグインで生成します。その仕様からクライアント側の通信部分のソースを、またプラグインで自動生成します。
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にプラグイン、ライブラリ、タスクを追加します
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に保存します。
ここで、ひとつ注意点なんですが、gradleのgenerateOpenApiDocsタスクを起動を起動してもOpenAPIのインタフェースのjsonが更新されない時があります。原因は不明です。うまくいくときといかないときがあります。
うまくいく場合はspringBootのサーバが起動され、多分gradleタスクの中でHTTPで取得してjsonに保存しているのだと思いますが、うまく行かない場合はspringBootのサーバ起動してません。
うまく更新されない場合は、MyApiServer.jsonを一旦削除すると更新されます。
もしくは gradleのオプション --rerun-tasks を付けると作成されます。
gradle generateOpenApiDocs --rerun-tasks
generateOpenApiDocsはデフォルトではjsonでopenAPI spec(仕様)を生成しますが、yamlにしたければ、
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
を参考に付けます。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
を参照してください。
Springdoc-openapiは従来のswagger-uiの機能も内包しています。これらの動作を変更する場合は、application.prpperties、application.ymlに記述します。
詳細は上記のSpringdoc-openapiのドキュメントの
5.1. springdoc-openapi core properties
5.2. swagger-ui properties
を参照してください。
OpenAPI Generator Gradle Pluginのドキュメントは以下を参照してください。
OpenAPI Generator Gradle Plugin
openAPI spec(仕様)からRetrofit2のkotlinクライアントを生成する
さて、springBoot3のサーバサイド側でControllerのアノテーションの仕様から生成したopenAPI spec(仕様)のファイルをAndroid側にコピーして持ってきます。
{プロジェクトルート}apiDocs/MyApiServer.json
に置きます。openAPI spec(仕様)からRetrofit2のクライアントノソースコードを自動生成してみます。
ます、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の中で、
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トップ
generators 生成可能なソースの一覧
クライアント側kotlinの生成
サーバ側kotlin-springの生成
openAPI spec(仕様)を編集するツール
openAPI spec(仕様)はテキスト(jsonまたは、yaml)なのでテキストエディタで編集可能ですが、折角なので専用のツールを使った方が効率がいいです。
Swagger editorは昔からのおなじみです。一応openAPI 3.xにも対応しています。
昔は、StopLightは使い勝手が良かったのですが、最近は何故か、Webの方に登録を強制されるので使いづらいです。ApiDocの方が日本語に対応しているのと、クライアント、サーバ側のソースをここから生成できるので、ApiDocの方が使い勝手がよさそうです。