OpenAPI Generator を使ってみたので、使用感やカスタマイズなどを紹介します
環境
- OpneAPI Generator 7.1.0
- OpenAPI Specification 3.0.3
- Spring Boot 3.1.5
- Amazon Corretto 17
- Kotlin 1.9.10
OpenAPI Generator とは
OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (both 2.0 and 3.0 are supported). Currently, the following languages/frameworks are supported:
引用元: https://github.com/OpenAPITools/openapi-generator#overview
一言でいうと、OpenAPI仕様からAPIクライアント、スタブサーバー、ドキュメントなどを自動生成してくれるツールです
OpenAPI Generator 利用背景
- フロントエンドとバックエンドを別チームで分業開発するため、OpenAPIを書いてSchema駆動で開発
- せっかくOpenAPIを書くなら自動生成することで、実装時間の短縮やドキュメントと実装の仕様一致などの恩恵を受けたい
build.gradle.kts の設定値
まずは build.gradle.kts の全体像を紹介します
最初は最低限の設定で生成してみましたが、すぐに扱いづらい部分が見えてきてどんどん設定を足していった結果これだけ肥大化していきました。。。
なお、設定できる項目は以下で確認できます(めちゃくちゃ多い)
import org.openapitools.generator.gradle.plugin.tasks.GenerateTask
plugins {
id("org.hidetake.swagger.generator") version 7.1.0
}
tasks.withType<GenerateTask> {
doFirst {
delete("$projectDir/src/main/kotlin/com/example/model")
}
generatorName.set("kotlin-spring")
inputSpec.set("$apiDocumentDir/openapi.yaml")
outputDir.set("$projectDir")
packageName.set("com.example")
modelPackage.set("com.example.model")
modelNameSuffix.set("Model")
templateDir.set("$apiDocumentDir/templates")
typeMappings.set(
mapOf("DateTime" to "LocalDateTime")
)
importMappings.set(
mapOf("LocalDateTime" to "java.time.LocalDateTime")
)
configOptions.set(
mapOf(
"annotationLibrary" to "none",
"documentationProvider" to "none",
"exceptionHandler" to "false",
"gradleBuildFile" to "false",
"interfaceOnly" to "true",
"serializationLibrary" to "jackson",
"useBeanValidation" to "true",
"useSpringBoot3" to "true",
)
)
ignoreFileOverride.set("$apiDocumentDir/.openapi-generator-ignore")
doLast {
delete("$projectDir/.openapi-generator")
}
}
以下で目的別に設定を解説します
1.使用する Generator の設定
generatorName.set("kotlin-spring")
タイトルの通り Kotlin + Spring Boot で使用するため、kotlin-spring
の設定で生成してもらいます
他言語のメジャーなものだとphp-laravel
やruby-on-rails
などの設定も存在
2.生成元/生成先の設定
inputSpec.set("$apiDocumentDir/openapi.yaml")
outputDir.set("$projectDir")
packageName.set("com.example")
modelPackage.set("com.example.model")
modelNameSuffix.set("Model")
doFirst {
delete("$projectDir/src/main/kotlin/com/example/model")
}
inputSpecで生成元となるOpenAPIを指定します。後の設定は出力先のディレクトリやパッケージの指定です。今回は意図せぬ差分を検知できるように生成コードもGit管理に含めており、自分達が実装したクラスと区別しやすいようにパッケージを分けたりクラス名に固定でModel
サフィックスをつけるような生成にしています。(Git管理せずbuild以下に出力するのもあり)
若干イケてないですが、Component名を変更して再生成すると元のComponent名で生成されたクラスが残ってしまうため、前処理で全削除して洗い替えされるようにしています。
3.生成コードのカスタマイズ設定
templateDir.set("$apiDocumentDir/templates")
typeMappings.set(
mapOf("DateTime" to "LocalDateTime")
)
importMappings.set(
mapOf("LocalDateTime" to "java.time.LocalDateTime")
)
configOptions.set(
mapOf(
"annotationLibrary" to "none",
"documentationProvider" to "none",
"exceptionHandler" to "false",
"gradleBuildFile" to "false",
"interfaceOnly" to "true",
"serializationLibrary" to "jackson",
"useBeanValidation" to "true",
"useSpringBoot3" to "true",
)
)
自動生成にはMustacheテンプレート
が使用されているため、自前でテンプレートを用意すれば出力内容を細かくカスタマイズできます
基本的にはデフォルトで使用されているテンプレートをコピーしてきて必要な部分を修正していく形です(元のファイル名と同じにする必要あり)
テンプレートも結構カスタマイズしているのですが、長くなるので別章で紹介します。
OpenAPIではstring型などにformatを指定できるのですが、そのformat指定時の生成型を変更することもできます。stringのdate-timeはデフォルトだとOffsetDateTimeで生成されるのですが、今回タイムゾーンは不要だったためLocalDateTimeで出力するように差し替えています。
configOptionsではさらに詳細な設定ができます。今回の設定内容でのポイントを紹介すると以下になります
- Request/Response Bodyに使うようなComponent定義のみをモデルクラスとして自動生成して欲しいため、不要な設定はoffにする
- JSONシリアライズ/デシリアライズには
Jackson
を使用 - requiredによる必須チェックやpatternやsizeなどの書式チェックも自動生成して欲しいため、
BeanValidation
を有効化
4.生成コードの除外設定
ignoreFileOverride.set("$apiDocumentDir/.openapi-generator-ignore")
doLast {
delete("$projectDir/.openapi-generator")
}
README.mdなどはオプションで自動生成offにできなかったため、ignoreファイルで管理して生成されないようにしています。が、ディレクトリだけは作られたまま残ってしまうので後処理で削除することに。。。
生成クラス例
さて、ここまででGeneratorの設定を紹介(Mustacheテンプレートのカスタマイズは除く)をしましたが、実際にどんな形でモデルクラスが生成されるのかを簡単に紹介します。
以下にOpenAPIの例(抜粋)と生成されるモデルクラスを掲載
components:
SampleResponse:
type: object
description: サンプルレスポンス
properties:
id:
type: integer
format: int32
description: ID
code:
type: string
pattern: '^[a-zA-Z0-9]+'
description: コード
name:
type: string
maxLength: 10
description: 名前
memo:
type: string
description: メモ
required:
- id
- code
- name
package jp.co.sample.model
import java.util.Objects
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonValue
import jakarta.validation.constraints.DecimalMax
import jakarta.validation.constraints.DecimalMin
import jakarta.validation.constraints.Email
import jakarta.validation.constraints.Max
import jakarta.validation.constraints.Min
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Pattern
import jakarta.validation.constraints.Size
import jakarta.validation.Valid
/**
* サンプルレスポンス
* @param id ID
* @param code コード
* @param name 名前
* @param memo メモ
*/
data class SampleResponseModel(
@field:NotNull
@get:JsonProperty("id") val id: kotlin.Int?,
@get:Pattern(regexp="^[a-zA-Z0-9]+")
@field:NotNull
@get:JsonProperty("code") val code: kotlin.String?,
@field:NotNull
@get:Size(max=10)
@get:JsonProperty("name") val name: kotlin.String?,
@get:JsonProperty("memo") val memo: kotlin.String?,
) {
}
requiredやpattern、maxLengthの内容もBeanValidationとして生成されており、descriptionの内容もdocとして生成されます。実際にはもっとフィールド数も多く、ObjectがネストしたComponentを定義しているのでこららが自動生成されるだけでかなり助かっています。
(一旦の)まとめ
ちょっと力尽きてきた 記事が長くなってきたので一旦ここで区切ろうと思います。来週あたりに続きとして「Kotlin + Spring Boot で OpenAPI Generator 体験記②(Mustacheテンプレート格闘編)」を書こうと思いますので投稿されたらそちらもみていただけると幸いです。
余談ですが、弊社去年のカレンダーの同じ日(12/2)にOpenAPI Generatorの記事が投稿されていたようです(運命感じますね)
(追記) 続きの記事を作成しました