冥冥乃志

ソフトウェア開発会社でチームマネージャをしているエンジニアの雑記。アウトプットは少なめです。

follow us in feedly

gradleのマルチプロジェクト環境でマイグレーション専用のプロジェクトを作った話

タイトルだけで何をやったか想像がついた方はお疲れ様でした。とりあえずなんだかんだはまりながらたどり着いたので、もっとできる人からもっと効率の良いやり方のまさかりが飛んでくることを期待しつつ。

背景

  • Webアプリケーションとバッチで構成されるサービス
  • Web側についてはアクセス数に応じてスケールさせやすくしておきたい
  • バッチはデータ量が極端に増えないのであまりスケールを考えなくてもいい
  • どうせ同じEntityモデルとか使うしWebとバッチでリポジトリは分けたくない

一応、Webアプリケーションとバッチはdocker(でなくてもいいけど)でスケールしやすい構成にしようかと思っています。

で、Webアプリケーションは複数だし、バッチもあるし、必ずしも両方リリースする戸言うことは考えられないし、デプロイ時にDDLのauto-generateやってしまうのはあまり得策ではないなあ、と思ったので、マルチプロジェクトにしてマイグレーション専用のサブプロジェクトを作ることにしました。

gradleのプロジェクト構成

  • master
  • shared -> 共有するクラス類。EntityやUtilityが中心
  • batch
  • webapp
  • migrate -> マイグレーションを行うプロジェクト

gradleのマルチプロジェクトの組み方については公式ドキュメントを見るなりしてください。ここは特に難しい話ではなかったし、でてくるサンプルと同じレベルのことしかしていません。すべてのプロジェクトに対してSpringBootに依存関係を取っています。

batch webapp migrate については shared と依存関係を取っていて、EntityやUtilityを共有しつつ、それぞれの目的にあったアプリケーションを構築する、というプロジェクト構成にしています。

マイグレーションプロジェクトでやりたいこと

  1. Flywayを使ったデータベースのバージョニングとマイグレーションの実行
  2. HibernateDDL-AUTO機能を使ったEntityとスキーマのバリデーション

本当はEntity修正したらDDL差分を作ってマイグレーションスクリプトに、とかやれるといいんですけどね、作る手間を考えてそこはレビューで潰す、という方向にしてます(とりあえず)。

Flywayを使ったマイグレーション

マイグレーションにはFlywayを使います。

Flyway by Boxfuse • Database Migrations Made Easy.

gradleプラグインの導入についても公式にドキュメントはありますが、これはシングルプロジェクトでの導入です。マルチプロジェクト環境で、狙ったプロジェクトにプラグインのタスクを反映させるには、公式のドキュメントの記載に加え、マイグレーションプロジェクトで apply plugin が必要です。

project(':migrate') {
    apply plugin: "org.flywaydb.flyway"


    dependencies {
        compile project(':shared')
    }
}

上記設定をしてから以下のgradleタスクを実行すると、マイグレーションプロジェクト配下にあるマイグレーションスクリプトを実行してくれます。

gradle :migrate:flywayMigrate -Dflyway.url=[JDBCのURL] -Dflyway.user=[データベースユーザ] -Dflyway.password=[パスワード]

マイグレーションがうまくいくと、スクリプトで実行したDDLのほかに schema_version というテーブルもできます。Flywayはこのテーブルを使ってマイグレーションスクリプトの実行を制御するので、これは削除しないように注意しましょう。

Flywayのgradleプラグインには、以下のとおりほかにもコマンドラインと同じ一通りの実行環境が揃っているので、gradleタスクを使って一通りのマイグレーション処理は実施できます。

Flyway tasks
------------
flywayBaseline - Baselines an existing database, excluding all migrations up to and including baselineVersion.
flywayClean - Drops all objects in the configured schemas.
flywayInfo - Prints the details and status information about all the migrations.
flywayMigrate - Migrates the schema to the latest version.
flywayRepair - Repairs the Flyway metadata table.
flywayValidate - Validates the applied migrations against the ones available on the classpath.

実行における設定は、

ビルドスクリプトに直接書く build.properties コマンドラインオプション project.ext

のいずれかを選択することができます。今回はコマンドラインオプションに指定しました。設定関連の詳細は以下を見てください。

gradle flywayMigrate - Documentation - Flyway by Boxfuse • Database Migrations Made Easy.

マイグレーションスクリプト内のプレースホルダーを指定可能(デフォルトは ${} )だったり、callbacksでFQCNを指定するとコールバック処理を指定することが可能だったり、結構高機能です。

Entityのバリデーション

Entityのバリデーションは、 spring.jpa.hibernate.ddl-auto の設定を validate で、コンテナを起動するだけのApplicationクラスを作って gradle :migrate:bootRun するだけです。

で、DBMSMySQLを検証環境を作ってみたのですが、幾つかつまらなーいことでハマってしまったのが恥ずかしい。

  • JDBCドライバが新しすぎてコネクションが確立できない *1
  • MySQLのboolean(内部的にtinyint)をJavaのbooleanにそのままマッピングできない *2
  • spring.jpa.hibernate.ddl-auto をOS環境変数で指定する際に綴りではまる *3

レビュー、リリース手順について

で、最終的に以下のようなコマンドでマイグレーションとEntityのバリデーションを一括でやってもらいます。

gradle :migrate:flywayMigrate -Dflyway.url="${SPRING_DATASOURCE_URL}" -Dflyway.user="${SPRING_DATASOURCE_USERNAME}" -Dflyway.password="${SPRING_DATASOURCE_PASSWORD}" :migrate:bootRun -i

これで、WebアプリケーションとバッチアプリケーションからテーブルのマイグレーションとEntityのバリデーションを切り離しました。これ以降の扱いや流れについては以下のようになるのかなあ、と思っています。

  • Entityの変更時には最新のデータベースに対してvalidationを行う
  • レビュアーもvalidationはしてみよう
  • マイグレーションしてからアプリケーションのリリースという手順
  • アプリケーション実行時にDDLの自動生成はしないしvalidationもしない

まあ、もっといい方法があれば変えていこうとは思っていますが。

*1:MySQLは5.7.12で一応最新の安定版

*2:JDBCのURLにtinyInt1isBit=falseを指定する必要あり

*3:ddl-autoをDDL_AUTOではなくDDLAUTOで指定していた