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を共有しつつ、それぞれの目的にあったアプリケーションを構築する、というプロジェクト構成にしています。
マイグレーションプロジェクトでやりたいこと
本当は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
するだけです。
で、DBMSはMySQLを検証環境を作ってみたのですが、幾つかつまらなーいことでハマってしまったのが恥ずかしい。
- 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もしない
まあ、もっといい方法があれば変えていこうとは思っていますが。