Migrationsプラグインの実践的運用

イントロダクション

CakeDCが提供しているMigrationsプラグインは、データベースのインクリメンタルなバージョン管理を行うプラグインで、githubでホストされています。
RoR(Ruby on Rails)のMigrationsをリスペクトしているのは間違いなさそう(どこにも書いてないけど)ですが、中身はそれなりに違います。
Migrationsプラグインの詳細な説明は
CakePHP Migrations plugin: easily version and deploy whole applications by Pierre Martin, Cake Development Corporation
を見てください(英語)。

バージョン管理システムとベストマッチ

CakeDCの記事にもある通り、Migrationsプラグインバージョン管理システムとの協調に大きく貢献します。
これは例えば、開発中はトピックブランチ*1を必ず作って、マージしても良いと判断されたらその大元(masterやdevelopブランチ)へマージする、といった運用がよく行われることと思います。
ここでトピックブランチ内でデータベースのスキーマを変更することは度々あることでしょう*2
これを、そのブランチの開発者の開発環境外*3で適用するにはどういった方法が考えられるでしょう?

  1. スキーマファイルを用い、cake schema update
  2. SQLダンプを都度適用する
  3. 手動(!)
  4. その他独自の手法

2番目と3番目の方法はあまりにも非効率的で、複数人で開発する時はもはや致命的と言っていいほどの運用でしょう。
スキーマで事足りている、というのならそれもまた是ですが、コールバックにやや不満が残るところです。
独自の方法は情報共有を密にしなければいけない他、学習コストも考えられます。
Migrationsプラグインを使えば、コマンド一発で全てのスキーマバージョンのコントロールが行えるため、スキーマの変更を伴う機能の追加・排除を統一的な手順によって行うことができます。

前提条件

この記事では上記のような、

で行うことを前提に、Migrationsプラグインを運用する実践的なTIPSの紹介をします。

バージョンの生成

バージョンとは、Migrationsプラグインが管理するスキーマ変更点を記述するもので、これをインクリメンタル*4に羅列することによって、アプリケーションのスキーマのロードマップが描かれます。
最初のバージョンは、全てのテーブル*5の作成という支持を与えることになります。
手動で全てを書くことももちろん不可能ではないですが、自動生成をすることが必要となってくるでしょう。
データベースのテーブル作成、変更、削除については多種多様なツールがあり(例えばMySQLAdmin)、それらを用いて行うのが通常だと思われます。
その変更点の差分を取ることによって、バージョンの自動生成を行うことになります。
差分を生成するには

cake migration generate

コマンドを叩きます。
基本的にはy, nを入力(テーブルの比較をするか、プレビューをするか)の後、最後にバージョンの名前を指定すると、バージョンが生成されます。

モデルが無いテーブル

デフォルトではモデルがあるテーブルのみが自動生成の対象となります(schemaと一緒ですね)。
これを回避するには、-f(force)オプションを指定します。

cake migration generate -f
Schemaファイルとバージョンの生成

バージョンの自動生成をする際、Schemaファイルがあると(app/config/schema/schema.php)、それと現在のDBスキーマを比較します。
逆にSchemaファイルが無いと、全てのテーブルがバージョンとして生成されます
つまり、差分をうまく吐くにはSchemaファイルが必要になってきます。
従って、バージョンを吐いた後に、スキーマファイルも(必要であれば上書きして)生成しておく必要があります。

cake migration generate -f
cake schema generate -f

さて、最初のバージョンを作成する方法は二通り考えられるでしょう。

  • 初期スキーマをまずSchemaファイルに書く
  • 初期スキーマをMigraionの一番目のバージョンファイルに書く

1番目の方法を取ると、ある大きな問題にぶち当たります
それは(例えば本番環境で)最初から一気にスキーマを適用しようとした時、Schemaファイルを実行するしか術が無いということです。
そして、Migrationsのバージョン管理(schema_migrationsテーブル)は、「何も適用されていない」ということになります。
これは次のバージョンアップがある場合、バージョンを適用するだけとはいかず、Migrationsを実行した場合と差異が出る場合があります。
これを避ける為に、2番目の方法を取る必要があります。
従って、以下のフローをお勧めします:

  1. MySQLAdminなどを使ってテーブルの作成
  2. Migrationバージョンの生成
  3. 次に差分を自動するためのSchemaファイルの生成

トピックブランチごとのバージョン管理

トピックブランチでスキーマの変更がある場合、そのトピックブランチ一つごとにバージョンを作成することは良い習慣となります。
トピックブランチを作業ブランチに取り入れた後、スキーマのバージョンを最新にするには以下のコマンドを実行します。

cake migration run all

ここまでは素直なフローですが、あるトピックブランチで新しいバージョンを適用した後、そのトピックブランチが適用されるまでに、他のトピックブランチで作業をしたい場合もあるかと思います。
そこでスキーマバージョンのダウングレードを行います。

cake migration run down

例えば上記を実行することでこれは達成できます。
注意することは、トピックブランチを移動する前にこれを実行しなければならないということです。
移動した後では、差分の情報があるファイルが失われてしまい、ダウングレードが正常にできないためです。

コールバックを用いた不整合の起きないバージョンの作成

Migrationsプラグインの大きな特徴はコールバックの整備です。
コールバックをふんだんに使うことによって、うまく整合性を保つことが運用において最重要となります。

初期レコード

Migrationsを使う場合、初期レコードの生成・更新はバージョンのコールバックを使って行うべきです。
Schemaでもこれは行えるのですが、バージョンごとに管理できる分だけMigrationsならリリースごとに初期レコードを挿入できる分非常に便利です。
初期レコードの更新を行う方法は様々ですが、レコードを配列でもっておき、それを挿入・更新するような実装になっていれば良いでしょう。

上記(抜粋)は一例です。

プライマリーキーの変更

静的なテーブルで、プライマリーキーを例えば整数からUUIDに変更したい場合*6、関連するモデルの外部キーも変更したいところです。
これを行うにあたって、InnoDBで外部キー制約をつけている場合、外部キー不整合が起きる為、エラーが起きることがあります*7
これを回避するために、そもそも外部キー制約をしないというのがひとつの方法となり得ます。
しかし、既に外部キー制約を用いたセットアップをしてある場合は一筋縄ではいかないでしょう。
そこで外部キー制約を一旦削除して、レコードの更新などを行なってから元に戻す、ということが必要になります*8
例の抜粋をあげます:

汎用化してる為見難いですが、やってる事自体は単純です!

最後に

その他、CSVからデータを読むも良し、fixturesを使うのも良し(テーブルのdropがされるのを回避できれば)です。
他にもCakeMigrationを継承したクラスで、共通のコールバックが定義できるなど、非常に柔軟で自由な制御ができます。
やりたいことを叶えるのに必要なのは、あなたのイマジネーションだけです!
プロジェクトに合わせた、自在なコントロールを叶えましょう。

21日目のCakePHP Advent Calendar 2011は、ogaaaanさんです。よろしくお願いします。

*1:特定の機能・修正についてのブランチ

*2:データベース設計を完全にしてから開発に入る?本当にそれで最後までうまくいった試しがありますか?

*3:例えばテスト環境、別の開発者の開発環境

*4:時系列順と捉えて良い

*5:schema_migrationsという、バージョンが適用されたかをMigrationsが管理するためのテーブルを除く

*6:実話

*7:実話

*8:SET FOREIGN_KEY_CHECKS=0が効かないのは周知の事実なのかやり方が間違ってるのか誰か教えて!