GeekFactory

int128.hatenablog.com

Swagger自動生成スタブの継続的デプロイ

SwaggerにはYAMLからAPIクライアントやAPIサーバのコードを自動生成する機能があります。コードの自動生成を利用することでドメインの実装に集中でき、また、スタブを利用することでチーム開発を円滑に進められるといったメリットがあります。

しかし、コードの自動生成を利用すると、API仕様を更新したのにスタブは古いままといったライフサイクルのズレが起こりがちです。そこで、CIで継続的に自動生成を実行することで、常に最新のスタブを共有しながらチーム開発を行えるようになります。

本稿では、CIとGradleを利用してスタブコードを自動生成する方法を説明します。具体的には、CIで以下を実行します。

  1. 開発者がSwagger YAMLを変更する。
  2. Gradleでスタブコードを自動生成する。
  3. GradleでスタブコードからJARをビルドする。
  4. JARをデプロイする。

前提

以下のマルチプロジェクト構成を前提に説明します。

  • ルートプロジェクト
    • api
      • src/main/swagger/api.yaml - API仕様
    • codegen
      • build.gradle - ビルドスクリプト
      • template/*.mustache - テンプレート(任意)
      • build/swagger-server - 自動生成コードの出力先
    • stub
      • build.gradle - ビルドスクリプト
      • build/libs/stub.jar - JARの出力先

Swaggerによるスタブコードの自動生成

ビルドスクリプトからswagger-codegen-cliを実行します。

// codegen/build.gradle
configurations {
    swagger
}

dependencies {
    swagger 'io.swagger:swagger-codegen-cli:2.2.1'
}

task generateSwaggerServer(type: JavaExec) {
  description 'Generate source code for API server'
  main = 'io.swagger.codegen.SwaggerCodegen'
  classpath = configurations.swagger
  args 'generate',
       '-i', file("${project(':api').projectDir}/src/main/swagger/api.yaml"),
       '-o', file("$buildDir/swagger-server"),
       '-l', 'spring',
       '--additional-properties', 'dateLibrary=java8'
}

swagger-codegen-cliに引数を渡すことで、自動生成されるコードをカスタマイズできます。上記の例では、Springのコードを生成する、Java 8の日付クラスを使うといったことを指定しています。

テンプレートのカスタマイズ

自動生成のテンプレートをカスタマイズするには、swagger-codegenリポジトリにあるテンプレートフォルダを使います。例えば、Springのテンプレートをカスタマイズするには以下のフォルダをローカルに配置します。

https://github.com/swagger-api/swagger-codegen/tree/master/modules/swagger-codegen/src/main/resources/JavaSpring

ビルドスクリプトではテンプレートの場所をswagger-codegen-cliの引数に渡します。

task generateSwaggerServer(type: JavaExec) {
  description 'Generate source code for API server'
  main = 'io.swagger.codegen.SwaggerCodegen'
  classpath = configurations.swagger
  args 'generate',
       '-i', file("${project(':api').projectDir}/src/main/swagger/api.yaml"),
       '-o', file("$buildDir/swagger-server"),
       '-t', file('template'),  // テンプレートの場所
       '-l', 'spring',
       '--additional-properties', 'dateLibrary=java8'
}

スタブのビルド

自動生成されたコードをビルドします。ここではSpring BootでJARを生成します。

// stub/build.gradle
buildscript {
    ext { springBootVersion = '1.4.0.RELEASE' }
    repositories { jcenter() }
    dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") }
}

plugins {
    id 'java'
    id 'spring-boot'
}

sourceCompatibility = 1.8
targetCompatibility = 1.8

sourceSets.main.java.srcDir file("${project(':codegen').buildDir}/swagger-server/src/main/java")
sourceSets.main.resources.srcDir file("${project(':codegen').buildDir}/swagger-server/src/main/resources")

repositories {
    jcenter()
}

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'io.springfox:springfox-swagger2:2.5.0'
    compile 'io.springfox:springfox-swagger-ui:2.5.0'
}

compileJava.dependsOn project(':codegen').generateSwaggerServer

buildタスクを実行すると、JARファイルが出力されます。

./gradlew :stub:build

スタブのデプロイ

/stub/build/libs/stub.jar に出力されたJARをサーバにデプロイします。Dockerを利用すると簡単に実行できるので便利です。

# stub/Dockerfile
FROM java:8
EXPOSE 8080
COPY build/libs/stub.jar /
ENTRYPOINT ["java", "-jar", "/stub.jar"]
# stub/docker-compose.yml
version: "2"
services:
  app:
    build: .
    ports:
      - "8080:8080"

以下のコマンドを実行すると、スタブが実行されます。古いスタブが実行されている場合は新しいものに置き換わります。

docker-compose build
docker-compose up -d

サーバの /v1 にアクセスしたらSwagger UIが表示されます。また、YAMLに定義済みのパスにアクセスしたら空のJSONが返ってきます。

今後の課題

swagger-codegen-cliが自動生成するコードは空の値を返す実装になっています。これではスタブの利用側に不親切なので、 example セクションに書いたデータを返す実装にしてほしいと思います。

Gradleからswagger-codegenを利用するプラグインはすでにありますが、swagger-codegen-cliの最新版に追いついていないので、今回はJARを直接実行することにしました。本当はプラグインを利用する方がビルドスクリプトの可読性が高くてよいと思います。