GeekFactory

int128.hatenablog.com

開発チームがプロダクトコードを書き始めるまでに準備すること

頭の中を整理するため、いわゆるZero Feature Releaseに必要なことをまとめてみました。開発チームがプロダクトコードを書き始めるまでに作っておくとよいものです。

  1. ソースコードリポジトリ(例:GitHub
  2. エディタ設定(例:editorconfig)
  3. ビルドツール(例:Gradle Wrapper)
  4. ビルドスクリプト(例:build.gradle)
  5. フレームワーク(例:Spring Boot)
  6. スティングフレームワーク(例:Spock)
  7. 静的解析ツール(例:Sonar
  8. ドキュメンテーションツール(例:Asciidoctor)
  9. DBマイグレーションツール(例:Flyway)
  10. CIスクリプト(例:Jenkinsfile)
  11. CIとソースコードリポジトリの連携(例:Jenkins Webhook)
  12. CIとチャットの連携(例:Slack)
  13. 成果物の公開場所(例:GitHub Releases)
  14. ライブラリの公開場所(例:Bintray Maven Repository)
  15. ドキュメントの公開場所(例:GitHub Pages)
  16. 実行環境(例:Heroku)
  17. プロビジョニングスクリプト(例:Docker Compose)

なお、プロジェクト管理や本番環境の運用に必要なものは書いていません。

こうやって書き出してみると少ない気がしますが、一つずつ積み上げていく必要があるので意外と時間がかかります。プロジェクトの初期にこのような仕組みを整備する時間を確保するとよいでしょう。

ちなみに、このような仕組みがないままプロダクトコードを書き始めると、本当に動くか分からないコードが量産されたり、特定の環境でしか動かないコードが出現したり、ソースコードリポジトリに存在しないけど実行環境にはリリースされているファイルが出現されたりします。

そんな辛い状況は、個人の努力で何とかする(筋肉運用)のではなく、チーム作業の進め方を変えて改善していきましょう。チームの裁量でワークフローを改善していける組織構造がよいですね。

Gradle Swagger Generator Pluginを公開します

GradleでSwagger YAMLからAPIサーバやAPIクライアント、APIドキュメントを生成するプラグインを作りました。

github.com

もともとはGradle Swagger Codegen Pluginという名前でしたが、コードだけでなくドキュメントの生成もできるようになったので名前を変更しました。

コードの自動生成

SpringベースのAPIサーバを生成するビルドスクリプトの例を以下に示します。

plugins {
  id 'org.hidetake.swagger.generator' version '1.4.0'
}

repositories {
  jcenter()
}

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

generateSwaggerCode {
  language = 'spring'
  inputFile = file('petstore.yaml')
}

実際は、APIサーバを自動生成するサブプロジェクトを作り、プロダクトコードのプロジェクトから依存関係を設定するとよいでしょう。自動生成コードに手を入れるとAPI仕様の変更を取り込むコストが大幅に高くなるので、サブプロジェクトのビルドでコード生成とコンパイルを行うとよいです。

コード生成にはSwagger Codegen CLIを利用しています。

ドキュメントの自動生成

APIドキュメントを生成するビルドスクリプトの例を示します。

plugins {
  id 'org.hidetake.swagger.generator' version '1.4.0'
  id 'org.asciidoctor.convert' version '1.5.3'
}

repositories {
  jcenter()
}

generateSwaggerDoc {
  inputFile = file('petstore.yaml')
}

参考までに、OpenAPIで公開されているpetstore.yamlから生成したAPIドキュメントを下記に置いています。

https://s3-ap-northeast-1.amazonaws.com/gradle-swagger-generator-plugin/SNAPSHOT/index.html

ドキュメント生成にはSwagger2MarkupとAsciidoctor Gradle Pluginを利用しています。

活用例

例えば、REST APIサーバを開発するプロジェクトでは以下のような活用例が考えられます。

  • APIサーバのコードを生成し、プロダクトコードから利用する。
  • APIクライアントのコードを生成し、JARをMaven Repositoryに公開する。
  • APIドキュメントを生成し、GitHub PagesやS3 Static Web Hostingで公開する。

リポジトリにはサンプルプロジェクトも置いてあるので、ぜひご活用ください。

github.com

GradleからS3に成果物を公開する

GradleからS3に成果物を公開する方法を調べたのでメモです。あらかじめIAMユーザを作成し、キーを取得しておく必要があります。

S3 Maven Repositoryに公開する

Gradleのmaven-publishプラグインを使うと、S3のMavenリポジトリに成果物を公開できます。詳しくは Maven Publishing (new) - Gradle User Guide Version 3.1 を参照してください。

ビルドスクリプトの例を示します。

// build.gradle
plugins {
    id 'java'
    id 'maven-publish'
}

// Travis CIでtag pushを契機にpublishを行う場合
version = System.getenv('TRAVIS_TAG') ?: 'SNAPSHOT'

publishing {
    repositories {
        maven {
            url 's3://your-bucket-name/folder'
            credentials(AwsCredentials) {
                accessKey System.getenv('AWS_ACCESS_KEY_ID')
                secretKey System.getenv('AWS_SECRET_ACCESS_KEY')
            }
        }
    }
    publications {
        mavenJava(MavenPublication) {
            groupId 'com.example'
            artifactId 'foo'
            version project.version
            from components.java
        }
    }
}

S3でファイルを公開する

S3にファイルをアップロードしたい場合はgradle-aws-pluginを使うとよいでしょう。生成したドキュメントをS3のStatic Web Hostingで公開するといった使い方が挙げられます。詳しくは GitHub - classmethod/gradle-aws-plugin: Gradle plugin to manage Amazon Web Services を参照してください。

ビルドスクリプトの例を示します。

plugins {
    id 'jp.classmethod.aws.s3' version '0.30'
}

// Travis CIでbranch pushを契機にpublishを行う場合
version = System.getenv('TRAVIS_BRANCH') ?: 'master'

task publishDocs(type: jp.classmethod.aws.gradle.s3.SyncTask) {
    source "$buildDir/docs"
    bucketName 'your-bucket-name'
    prefix "${project.version}/"
}

Static Web Hostingを使う場合は、バケット内のファイルを公開するBucket Policyを設定しておくとよいでしょう。SyncTaskではアクセス権まで設定できないようです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::your-bucket-name/*"
            ]
        }
    ]
}