概要
- WebAPIの設計書としてOpenAPI Specification(以下、OpenAPI Spec)を採用する
- フロントエンド、バックエンド共にOpenAPI Specからソースコードを自動生成する
- 本記事はバックエンドのみを対象とする
- 自動生成したクラスをSpring-bootのRESTfulなAPIとして組み込む
前提条件
OpenAPI Spec
周辺ツールの対応状況を鑑みてv3.0.0を前提とする。
Docker
OpenAPI Specからソースコードを自動生成ツールとしてDocker版のOpenAPI Generatorを使用するため。
CLIやGradleプラグイン等あるが、グローバルな環境を汚さずにフロントエンド、バックエンド共に使用するツールを揃えられるのでDocker版を使用する。
Java
Spring-bootでのバックエンドの開発においてもはやインストールされていて当たり前なものなので割愛。
なおバージョンは執筆時点で安定版として息の長いJava 17を採用。
Spring-boot
3.0.0は執筆時点ではSNAPSHOTなのでv2系最新の2.7.2を前提とする。
事前準備
プロジェクト作成
Spring Initializrを使用し、適当なプロジェクトを作成。
初手で必要なライブラリは以下の画像を参照。
RESTfulなAPIの自動生成
ポイントは2つ。
- 自動生成されるファイルの出力先をGradleのビルドディレクトリとする
- 一般的にビルドディレクトリ(Gradleであれば
build
ディレクトリ)は.gitignore対象とするため、リポジトリの管理対象外となる
- 自動生成されるファイルは手動での改変を許可せず、インプット(今回であればOpenAPI Spec)が同じであれば、いつ・誰が・どこで自動生成してもアウトプットが同じとなるため変更管理が不要なファイルのため
- 自動生成されるファイルを改変したい場合はGeneration-Gapパターン等を用いて、自動生成されるファイルそのものは改変されない仕組みを考えるべき
- Javaのソースコードディレクトリを
gen-src/main/java
とする
- 自動生成されたソースコードであることを明示的に分かりやすくするために変更する
- 後述するbuild.gradleの設定によってコンパイル対象として検出させる
docker run --rm \
-v "${PWD}/build/openapi:/out" \
openapitools/openapi-generator-cli:v6.0.1 generate \
--generator-name spring \
--input-spec https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/3_0/petstore.yaml \
--output /out \
--additional-properties hideGenerationTimestamp=true \
--additional-properties sourceFolder=gen-src/main/java \
--additional-properties basePackage=com.sliber_kugel.openapi \
--additional-properties apiPackage=com.sliber_kugel.openapi.adapter.controller \
--additional-properties modelPackage=com.sliber_kugel.openapi.domain.model \
--additional-properties skipDefaultInterface=true \
--additional-properties oas3=true \
--additional-properties dateLibrary=java8 \
--additional-properties delegatePattern=false \
--additional-properties interfaceOnly=true \
--additional-properties openApiNullable=false \
--additional-properties useTags=true \
--additional-properties disallowAdditionalPropertiesIfNotPresent=false \
--additional-properties useBeanValidation=true \
--additional-properties serializableModel=true
build.gradleの修正
// OpenAPI Generatorによって自動生成されるディレクトリをコンパイル対象として追加
sourceSets {
main {
java {
srcDirs = ['src/main/java', "${buildDir}/openapi/gen-src/main/java"]
}
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// 以下2つを追加
implementation 'io.swagger.core.v3:swagger-annotations:2.2.2'
implementation 'jakarta.validation:jakarta.validation-api:2.0.2'
}
本題
RestControllerの実装
ポイントは2つ。
- 自動生成されたインターフェースを
implements
する
- RESTfulなAPIのインターフェースが自動生成されているので実装クラスを実装する
@RestController
アノテーションを付与する
- 自動生成されたインターフェースをSpringのRESTfulなAPIとして認識させる必要があるためアノテーションを付与
package com.sliber_kugel.openapi.adapter.controller;
import com.sliber_kugel.openapi.domain.model.ModelApiResponse;
import com.sliber_kugel.openapi.domain.model.Pet;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController
public class PetController implements PetApi {
@Override
public ResponseEntity<Pet> addPet(Pet pet) {
// some implementation
throw new UnsupportedOperationException();
}
@Override
public ResponseEntity<Void> deletePet(Long petId, String apiKey) {
// some implementation
throw new UnsupportedOperationException();
}
@Override
public ResponseEntity<List<Pet>> findPetsByStatus(List<String> status) {
// some implementation
throw new UnsupportedOperationException();
}
@Override
public ResponseEntity<List<Pet>> findPetsByTags(List<String> tags) {
// some implementation
throw new UnsupportedOperationException();
}
@Override
public ResponseEntity<Pet> getPetById(Long petId) {
// some implementation
throw new UnsupportedOperationException();
}
@Override
public ResponseEntity<Pet> updatePet(Pet pet) {
// some implementation
throw new UnsupportedOperationException();
}
@Override
public ResponseEntity<Void> updatePetWithForm(Long petId, String name, String status) {
// some implementation
throw new UnsupportedOperationException();
}
@Override
public ResponseEntity<ModelApiResponse> uploadFile(Long petId, String additionalMetadata, MultipartFile file) {
// some implementation
throw new UnsupportedOperationException();
}
}