第1回 OpenAPI Generator を使ったコード生成

| 9 min read
Author: shigeki-shoji shigeki-shojiの画像

庄司です。

REST API の仕様を記述する OpenAPI Specification があります。この仕様では、JSON または YAML で API の仕様を記述します。

この業界標準の仕様で API を定義すると、開発に利用しやすいフォーマットされたドキュメントの提供やテスト用のモックを提供できます。

この定義を使うツールに OpenAPI Generator があります。これを使用すると、さまざまな言語、フレームワークの API Client、あるいは Server のスタブコードを生成できます。

Information

OpenAPI のような Schema 定義言語によって規定して開発する SDD (スキーマ駆動開発) は、日本CTO協会が監修・編纂しているDX Criteriaでも良いプラクティスとして SYSTEM-5-4 等であげられています。

初回のこの記事では、OpenAPI Generator を使って簡単な Spring Boot アプリを作成します。

この記事のコードは、GitHub リポジトリにあります。

本題に入る前に、このシリーズの記事の紹介を先にしたいと思います。

ドメイン駆動設計 (DDD - Domain-driven design) の主に戦略的設計で活用するイベントストーミングと、サイドカーパターンを紹介します。

OpenID Connect の ID トークンである JWT (正確には JWS) を使う認証認可等の横断的関心事は、ドメインロジックを実装する Spring Boot を使ったアプリケーション本体には組み込まず、サイドカーで処理をする多層アーキテクチャ (Multi-tier Architecture) で説明します。

参考: 「ドメイン駆動設計と「コントロールプレーン」、「データプレーン」

OpenAPI Generator のようなコード生成の活用でポイントとなる Generation Gap パターンについて説明します。

ドメイン駆動設計 (DDD) の戦術的設計によってサービスの実装を完成します。

このシリーズを通して採用している多層アーキテクチャ (Multi-tier Architecture) の1層である、サイドカーパターンで Open Policy Agent を使ってサービス全体を完成します。

OpenAPI Specification

#

YAML 形式でサンプル API を記述します。

openapi: 3.0.2
info:
  version: 0.1.0
  title: example
servers:
  - url: 'http://localhost:8080/example'
paths:
  /hello:
    get:
      description: Hello World
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Hello'
      tags:
        - example
components:
  schemas:
    Hello:
      type: object
      properties:
        message:
          type: string
          example: 'Hello World'
      x-tags:
        - example

この例では、/{base-path}/hello パスの GET メソッドを受け入れる API を定義しています。レスポンスは HTTP Status が 200 のみで、Hello と定義されたスキーマ の JSON を返します。

生成するモジュール構成

#

ここでは、application と controller の2つのモジュール (jar) を生成します。

  • application モジュールは、Spring Boot アプリケーションとして実行するために必要なクラスが生成され、実行可能 jar としてビルドされます。
  • controller モジュールは、OpenAPI Specification で定義された API ハンドラとなるインターフェース、リクエストおよびレスポンスのクラスのコードが生成されます。

生成するモジュールをなぜこのように分離するかというと、生成したコードに直接手を入れて、再生成した時にコードが壊れることを避けたいからです。つまり、Generation Gap パターンを適用したいためです。

application モジュールの生成とビルド

#

OpenAPI Generator を使用するためには、Java を実行する環境が必要です。また、生成後には Maven を使ってビルドすることになるため、DockerHub にあるオフィシャルな maven イメージを使用します。

モジュールを生成するプロジェクトルートに、前述の API 定義を openapi.yaml ファイルとして置きます。

次に、application モジュールを生成するパラメータファイルとして application.yaml ファイルを次の内容で記述します。

inputSpec: 'openapi.yml'
generatorName: spring
outputDir: modules/application
additionalProperties:
  configPackage: 'com.mamezou_tech.example.controller.configuration'
  modelPackage: 'com.mamezou_tech.example.controller.model'
  apiPackage: 'com.mamezou_tech.example.controller.api'
  invokerPackage: 'com.mamezou_tech.example.controller.api'
  groupId: 'com.mamezou_tech.example-service'
  dateLibrary: java8
  java8: true
  library: spring-boot
  artifactId: 'example-application'
  artifactVersion: '0.1.0'
  snapshotVersion: 'true'
  useTags: true

プロジェクトのルートで、Docker コマンドを実行します。

docker run -it --rm -v `pwd`:/build -v example:/root/.m2 maven:3.8-eclipse-temurin-17-focal bash

起動したコンテナ上で、次のコマンドを実行します。

curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.0.0/openapi-generator-cli-6.0.0.jar -o /tmp/openapi-generator-cli.jar
cd /build
java -DsupportingFiles -jar /tmp/openapi-generator-cli.jar batch application.yaml

ここまでの実行で /build/modules/application ディレクトリにプロジェクトが生成されます。

cd modules/application
mvn install

生成したアーティファクトは、example ボリュームに保存されます。

controller モジュールの生成とビルド

#

openapi.yaml ファイルを作成したのと同じ場所に controller.yaml ファイルを次の内容で作成します。

inputSpec: 'openapi.yml'
generatorName: spring
outputDir: modules/controller
additionalProperties:
  configPackage: 'com.mamezou_tech.example.controller.configuration'
  modelPackage: 'com.mamezou_tech.example.controller.model'
  apiPackage: 'com.mamezou_tech.example.controller.api'
  invokerPackage: 'com.mamezou_tech.example.controller.api'
  groupId: 'com.mamezou_tech.example-service'
  dateLibrary: java8
  java8: true
  library: spring-boot
  artifactId: 'example-controller'
  artifactVersion: '0.1.0'
  interfaceOnly: true
  snapshotVersion: 'true'
  useTags: true

プロジェクトのルートで、Docker コマンドを実行します。

docker run -it --rm -v `pwd`:/build -v example:/root/.m2 maven:3.8-eclipse-temurin-17-focal bash

起動したコンテナ上で、次のコマンドを実行します。

curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.0.0/openapi-generator-cli-6.0.0.jar -o /tmp/openapi-generator-cli.jar
cd /build
java -jar /tmp/openapi-generator-cli.jar batch controller.yaml

ここまでの実行で /build/modules/controller ディレクトリにプロジェクトが生成されます。

cd modules/controller
mvn install

生成したアーティファクトは、application モジュール同様、example ボリュームに保存されます。

Information

開発者が一人の場合は、この手順のようにローカルのストレージにアーティファクトを保存しても問題ありません。しかし、複数人で開発する場合には、Sonatype Nexus や JFrog Artifactory、AWS CodeArtifact などに deploy して共有することになるでしょう。

コードを完成させる

#

まず、modules/controller に生成されたコードを見てみましょう。

src/main/javacom.mamezou_tech.example.controller.api パッケージに ExampleApi インターフェースのコードが見つかります。

コードは controller モジュールに生成された ExampleApi インターフェースを実装することで完成します。

  1. プロジェクトのルートに、src/main/java ディレクトリを作成します。
  2. 作成したディレクトリに、com.mamezou_tech.example.controller.api パッケージを作成します。
  3. 作成したパッケージに ExampleApiController クラスを作成します。

ExampleApiController クラスのコードは次のようになりました。

package com.mamezou_tech.example.controller.api;

import com.mamezou_tech.example.controller.model.Hello;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("${openapi.exampleService.base-path:/example}")
public class ExampleApiController implements ExampleApi {

    @Override
    public ResponseEntity<Hello> helloGet() {
        Hello hello = new Hello();
        hello.setMessage("Hello World!");
        return new ResponseEntity<>(hello, HttpStatus.OK);
    }
}

このコードでは application.properties の openapi.exampleService.base-path、環境変数の OPENAPI_EXAMPLESERVICE_BASE_PATH 等に値が指定されていない場合のデフォルトベースパスを /example に指定しています。つまり、API 定義の /hello/example/hello になります。

Information

Spring Boot では application.properties の server.servlet.context-path、環境変数の SERVER_SERVLET_CONTEXT_PATH 等でコンテキストパスの設定もできます。例えば、環境変数に SERVER_SERVLET_CONTEXT_PATH=/stg と設定すると、/example/hello/stg/example/hello が有効なパスになります。

ビルド

#

コードを実装するプロジェクトでは人気のある Gradle を使ってみます。

プロジェクトのルートに、以下の内容の build.gradle ファイルを作成します。Spring Boot 等のバージョンは、application モジュールの pom.xml を参考にしています。

plugins {
    id 'org.springframework.boot' version '2.7.0'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

configurations {
    all {
        resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
    }
    unzipFile
}

group = 'com.mamezou_tech.example-service'
version = '0.1.0-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
    mavenLocal()
}

dependencies {
    implementation     'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    implementation     ('com.mamezou_tech.example-service:example-controller:0.1.0-SNAPSHOT') {
        changing = true
    }
    unzipFile          ('com.mamezou_tech.example-service:example-application:0.1.0-SNAPSHOT') {
        changing = true
    }
}

test {
    useJUnitPlatform()
}

task unzip(type: Copy) {
    def file = configurations.unzipFile.files.find {
        'example-application-0.1.0-SNAPSHOT.jar'
    }
    from zipTree(file)
    into 'build/unzipFile'
}

task copy(type: Copy) {
    from fileTree('build/unzipFile/BOOT-INF/classes')
    into 'build/classes/java/main'
}

tasks.copy.dependsOn(tasks.unzip)
tasks.compileJava.dependsOn(tasks.copy)

Gradle のイメージは DockerHub にあるオフィシャルなイメージを使います。

docker run -it --rm -v `pwd`:/build -v example:/root/.m2 -p 8080:8080 gradle:jdk17-focal bash

起動したコンテナ上で、次のコマンドを実行します。

cd /build
gradle bootRun

ホストPCのブラウザから、次の URL にアクセスします。

  • http://localhost:8080/example/hello

ブラウザには、以下のようなレスポンスが表示されます。

{"message":"Hello World!"}

次の URL で、API の仕様を実行中のアプリケーションから取得できます。

  • http://localhost:8080/swagger-ui/index.html

まとめ

#

この記事では、OpenAPI Generator を使用して Spring Boot のサーバーアプリケーションが容易に作成できることを説明しました。

次回以降の記事で、ここで作成したサンプルをさらに深掘りして説明します。

豆蔵では共に高め合う仲間を募集しています!

recruit

具体的な採用情報はこちらからご覧いただけます。