§データベース・エボリューション
リレーショナル・データベースを使う場合、スキーマ・エボリューションを追跡・整理する方法が必要になります。特に、次のような場合には、データベース・スキーマへの変更を追跡するために高度なツールを利用したくなると思います。
- チームで開発を行なっていて、各メンバーがスキーマへのすべての変更を知っておく必要がある
- アプリケーションを本番サーバにデプロイするにあたって、データベース・スキーマを安全にアップグレードしたい
- 複数台のマシンで開発を行なっていて、全マシンでデータベース・スキーマを同期させたい
§エボリューション・スクリプト
Play はエボリューション・スクリプトによってデータベース・エボリューションを追跡します。このスクリプトはただの SQL で記述し、アプリケーションの conf/evolutions/{データベース名}
というディレクトリに保存することになっています。デフォルト・データベースにエボリューションを適用したい場合は、スクリプトの保存先は、 conf/evolutions/default
になります。
最初のスクリプトは 1.sql
、2番目のスクリプトは 2.sql
、…というように名前をつけます。
各スクリプトはそれぞれ二つのパートで構成されています。
- Ups パートは、必要なスキーマの変換方法の記述
- Downs パートは、上記の変換をもとに戻す方法の記述
試しに、基本的なアプリケーションを始めるための最初のエボリューション・スクリプトを作成してみましょう。
# Users スキーマ
# --- !Ups
CREATE TABLE User (
id bigint(20) NOT NULL AUTO_INCREMENT,
email varchar(255) NOT NULL,
password varchar(255) NOT NULL,
fullname varchar(255) NOT NULL,
isAdmin boolean NOT NULL,
PRIMARY KEY (id)
);
# --- !Downs
DROP TABLE User;
ご覧のとおり、Ups パートと Downs パートはコメントで区切る必要があります。
データベースが application.conf
で設定されていて、エボリューション・スクリプトが存在する場合は、エボリューションが自動的に有効化されます。エボリューションを無効化したい場合は、 evolutionplugin=disabled
という設定を行なってください。例えば、自前でデータベースをセットアップするような自動化テストを書くような場合は、テスト環境においてエボリューションを無効化する設定を行いましょう。
エボリューションが有効化されていると、Play は開発モードの場合リクエスト毎に、本番モードの場合アプリケーションの起動前にスキーマの状態をチェックします。開発モードでは、データベース・スキーマが最新でない場合、適切な SQL スクリプトを実行してデータベース・スキーマを同期させるように促すエラーページが表示されます。
SQL スクリプトの内容に問題がなければ、この Apply evolutions
ボタンをクリックすることで即座にスクリプトを実行することができます。
§同時に行われた変更を同期させる
仮に、プロジェクトに二人の開発者がいるとします。開発者 A は新しいテーブルを必要とする新機能を担当しています。そこで、開発者 A は次のような 2.sql
を作成します。
# Post 追加
# --- !Ups
CREATE TABLE Post (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
content text NOT NULL,
postedAt date NOT NULL,
author_id bigint(20) NOT NULL,
FOREIGN KEY (author_id) REFERENCES User(id),
PRIMARY KEY (id)
);
# --- !Downs
DROP TABLE Post;
Play はこのエボリューション・スクリプトを開発者 A のデータベースで実行します。
一方、開発者 B は User テーブルのスキーマ変更を要する新機能を担当しています。そこで、開発者 B は次のような 2.sql
を作成します。
# User 変更
# --- !Ups
ALTER TABLE User ADD age INT;
# --- !Downs
ALTER TABLE User DROP age;
開発者 B は担当している機能を実装し終わったあと、それをコミットします (彼らが Git を使っていることにしましょう)。すると、開発者 A は作業を続行するにあたって、チームメイトが行った作業をマージする必要があります。彼は git pull を実行しますが、マージにはコンフリクトがあります。
Auto-merging db/evolutions/2.sql
CONFLICT (add/add): Merge conflict in db/evolutions/2.sql
Automatic merge failed; fix conflicts and then commit the result.
二人の開発者がそれぞれ 2.sql
を作成したため、開発者 A はこのファイルの内容をマージしなければなりません。
<<<<<<< HEAD
# Post 追加
# --- !Ups
CREATE TABLE Post (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
content text NOT NULL,
postedAt date NOT NULL,
author_id bigint(20) NOT NULL,
FOREIGN KEY (author_id) REFERENCES User(id),
PRIMARY KEY (id)
);
# --- !Downs
DROP TABLE Post;
=======
# User 変更
# --- !Ups
ALTER TABLE User ADD age INT;
# --- !Downs
ALTER TABLE User DROP age;
>>>>>>> devB
このマージは簡単ですね。
# Post 追加と User 変更
# --- !Ups
ALTER TABLE User ADD age INT;
CREATE TABLE Post (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
content text NOT NULL,
postedAt date NOT NULL,
author_id bigint(20) NOT NULL,
FOREIGN KEY (author_id) REFERENCES User(id),
PRIMARY KEY (id)
);
# --- !Downs
ALTER TABLE User DROP age;
DROP TABLE Post;
このエボリューション・スクリプトが表すデータベースのリビジョン 2 は、最初に開発者 A が適用したリビジョン 2 とは内容が異なります。
このとき、 Play はこの二つのリビジョンの内容の違いを検出して、まずは現在適用されている古い方のリビジョン 2 への変更を取り消し、それから新しい方のリビジョン 2 のスクリプトを適用します。
§不整合状態
たまにエボリューション・スクリプトに間違いがあって、適用に失敗してしまうこともあるでしょう。その場合、 Play はデータベース・スキーマを不整合状態としてマークします。そして、続行する前に問題を手動で解決するようにあなたに促します。
例として、Ups スクリプトにエラーを含む次のようなエボリューションを作成してみましょう。
# User にカラムを追加
# --- !Ups
ALTER TABLE Userxxx ADD company varchar(255);
# --- !Downs
ALTER TABLE User DROP company;
このエボリューションの適用は失敗し、 Play はデータベース・スキーマを不整合状態としてマークします。
続行するためには、まずこの不整合を解決する必要があります。今回は、正しい SQL コマンドを実行します。
ALTER TABLE User ADD company varchar(255);
次に、ボタンをクリックして、不整合を手動で解決したことを Play に知らせます。
しかし、エボリューション・スクリプトはまだ間違ったままなので、修正したいところです。そこで、以下のように 3.sql
を編集します。
# User にカラムを追加
# --- !Ups
ALTER TABLE User ADD company varchar(255);
# --- !Downs
ALTER TABLE User DROP company;
Play はこの新しいエボリューションを検知して、以前のリビジョン 3 と置き換え、適切なスクリプトを実行します。これで、全ての間違いが修正されて、本来の作業に戻ることができます。
ただし、開発モードでは単に開発用のデータベースを破棄して、全てのエボリューションを最初から適用しなおす方が簡単なことも多々あります。