AlterCheck

Alto DBFlute のパートです。

AlterCheckのコンセプト

検証のロジック

差分DDLを検証する仕組みがあります(@since 0.9.8.4)AlterCheck と呼ばれるこの仕組みを利用してフルDDLと差分DDLをしっかりと整合性をとって運用していくことが推奨されます。

前のDB(PreviousDDL) + 差分DDL(AlterDDL) = 最新のフルDDL(NextDDL)

前のDBに差分DDLを加えたものと、最新のフルDDLの実行結果は同じはずです。ReplaceSchema で双方の状態を作り出して差分をチェックします。(ReplaceSchema運用していることが前提)

言葉の定義

例えば、二週間ごとに開発とリリースを繰り返す インクリメンタル開発 として、その一つの スプリント の中で複数のDB変更が発生することを想定して説明をしていきます。

PreviousDDL
ひとつ前のスプリントのリリース直後の本番DBを再現できるフルDDL (+ データ)
AlterDDL
いまのスプリントのDB変更を実現する差分DDL (+ スクリプト)
NextDDL
いまのスプリントの最新のスキーマを再現できるフルDDL (+ データ)

"フルDDL" は、ERDツールなどから自動生成される create table 文が主体のもので、ReplaceSchema で実行されることを想定しています。 "差分DDL" は、人が手動で作成するもので、リリース時に本番DBに実行されることを想定しています。

AlterCheckの機能概要

二つのDBFluteタスク

DB変更のために二つのDBFluteタスクが用意されています。

SavePrevious
PreviousDDLを保存、前回リリース直後に実施、スプリント内で一回だけ
AlterCheck
AlterDDLをチェック、DB変更のたびに実施、スプリント内で複数回あり得る

SavePreviousを実行する人と、AlterCheckを実行する人が同じ人とは限りません(実行するタイミングが全然違う)。 また、複数のAlterCheckも同じ人とは限りません。それぞれ独立した作業となります。

チェックの流れ

ひとつのスプリントの中で "3回" DB変更があるとした場合、おおまかな流れはこのようになります。

  1. リリース作業: 前回スプリントのDB変更を本番DBに反映
  2. PreviousDDLを保存: SavePrevious
  3. (...通常の開発)
  4. DB変更作業: ERDの修正などでNextDDLを更新
  5. AlterDDLをチェック: AlterCheck
  6. (...通常の開発)
  7. DB変更作業: ERDの修正などでNextDDLを更新
  8. AlterDDLをチェック: AlterCheck
  9. (...通常の開発)
  10. DB変更作業: ERDの修正などでNextDDLを更新
  11. AlterDDLをチェック: AlterCheck
  12. (...通常の開発)
  13. リリース作業: チェック済みAlterDDLを本番DBに反映
  14. PreviousDDLを保存: SavePrevious (以降、繰り返し)

AlterCheckで使うディレクトリたち

ざっくり頭に入れておきましょう。

e.g. AlterCheckのディレクトリ構造の全体ざっくり @Directory
dbflute_maihamadb
 |-playsql
    |-data // ReplaceSchemaの登録データのディレクトリ
    |
    |-migration // AlterCheck用のディレクトリのトップ
    |  |
    |  |-alter  // AlterDDLを書くディレクトリ (一時作業領域なのでgitignore推奨)
    |  |  |-alter-schema.sql
    |  |  |-alter-schema-[チケット番号].sql
    |  |
    |  |-history // AlterCheckのSQLファイルの履歴ディレクトリ
    |  |  |-[日付]
    |  |  |  |-[日時]
    |  |  |  |  | // 本番DBにリリース済みのAlterDDL
    |  |  |  |  |-finished-alter...zip // (用済みなのでgitignoreでもOK)
    |  |  |  |
    |  |  |  |-[日時]
    |  |  |     | // 未リリースでチェック済みのAlterDDL @until DBFlute-1.2.0
    |  |  |     |-checked-alter...zip
    |  |  |
    |  |  | // 未リリースでチェック済みのAlterDDL @since DBFlute-1.2.1
    |  |  |-unreleased-checked-alter
    |  |     |-DONT_EDIT_HERE.dfmark
    |  |     |-for-previous-20120804-1746.dfmark
    |  |     |-READONLY_alter-schema.sql
    |  |     |-READONLY_alter-schema-[チケット番号].sql
    |  |
    |  |-previous // SavePrevious用のディレクトリ
    |  |  |-previous-20120804-1746.zip // 前のDBを再現するPreviousDDL
    |  |
    |  |-schema // 様々なスキーマ情報のディレクトリ
    |  |  |-alter-check-result.html // チェック結果が記載されているHTML

AlterCheckを実施する人が意識するのは、alterschema です。 チェックされた AlterDDL を本番に反映する人が意識するのは、unreleased-checked-alter です。

gitignoreの補足がないものは、基本的にバージョン管理(gitなど)のコミット対象にした方が良いものです。 (中にはコミットの必要ないものも幾つかありますが、一時的に作成されてコミット時にはすでに削除されるものなので、気にしなくても良いでしょう)

AlterCheck概念図

図 : AlterCheck概念図 AlterCheck概念図

リリース直後に SavePrevious

ひとつのスプリントで一回だけ!

DB変更する前(前回リリース直後)に、DB変更前のDBを復元できる DDL やデータをまるごと保存します。これが(リリースするまでは)本番DBと同じ構造を表現する PreviousDDL となります。

SavePreviousの実行の仕方

Manageタスクで、SavePrevious を指定して実行します(@since 0.9.9.7B)

e.g. Manageタスクで SavePrevious の実行 (シェル) @Command
...$ sh manage.sh save-previous

 or

...$ sh manage.sh   // Enter, and then select the number

PreviousDDLがZIPで保存される

すると、playsql 直下の DDL やデータがまるごと playsql/migration/previous 配下にコピー保存されます。さらに、妥当性をチェックするために内部的に ReplaceSchema が実行されます。正常に終了した場合は、migration 配下に previous-OK.dfmark (単に OK を強調するマーク) が作成されます。

e.g. PreviousDDL が正常に保存された場合 @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-previous
    |  |  |-previous-20120804-1746.zip // saved at 2012/08/04 17:46
    |  |-previous-OK.dfmark

万が一、開発前にこの課程を忘れてしまった場合でも、後でバージョン管理システムから直前のバージョンを取得して SavePrevious しても AlterCheck は利用できます。

前のスプリントのAlterDDLをfinish

SavePreviousは、これからのスプリントの準備を整える役割もあります。前のスプリントのAlterDDLを回収して history の ZIP に固めます。

DBFlute-1.2.1 より
unreleased-checked-alter のSQLファイルを finished-alter...zip に固める
unreleased-checked-alter は空っぽになる (その状態をgitにコミットする)
DBFlute-1.2.0 まで
checked-alter...zip を finished-alter...zip に変更

これにより、新たなスプリントの AlterCheck を実施することができます。

DB変更したら AlterCheck

ひとつのスプリントで何度でも

AlterCheckやるならIntro推奨

AlterCheckをやるのであれば、DBFlute Intro の画面がオススメです。

ファイル名のルールやディレクトリ構造を意識せずに安全に実施することができます。 特別な理由がない限りは、DBFlute Intro で AlterCheck を実施しましょう。

※仕組みや流れを把握しておくという意味で、以下の詳しい手順にも目を通しておくと良いでしょう。

(0. DB変更でNextDDLを更新)

NextDDLを更新する前にAlterCheckはできませんので、先にERDを修正するなどして NextDDL を更新し、ReplaceSchemaで今回のDB変更が含まれた状態を作り出せるようにしましょう。

1. いきなりAlterCheckでウォーミングアップ

alter文はどこに書けばいいのか!? 差分ってどんな風にみれるのか!? まずは何も考えず何の準備もせず、そのままAlterCheckタスクをいきなり実行してみてみましょう。

Manageタスクで、AlterCheck を指定して実行します(@since 0.9.9.7B)

e.g. Manageタスクで AlterCheck の実行 (シェル) @Command
...$ sh manage.sh alter-check

 or

...$ sh manage.sh   // Enter, and then select the number

2. チェック結果HTMLで差分の確認 (schemaディレクトリ)

いまこの状態では、AlterDDLがないので PreviousDDL と NextDDL で差があるはずです。 AlterCheckタスクは中断され(Failure)、前のスキーマと今のスキーマの純粋なDB変更の差分が検知されます。その差分は migration/schema 配下の alter-check-result.html に出力されます。

e.g. AlterCheck の結果 @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-...
    |  |-schema
    |     |-alter-check-result.html

いま自分がDB変更した想定される差分だけであることを確認しましょう。

PreviousDDL + AlterDDL" から "NextDDL" への差 になります。例えば、テーブルが追加(ADD)されていたら、"AlterDDL でそのテーブルを追加(ADD)しましょう" と解釈できます。

Previous
PreviousDDL + AlterDDL
Next
NextDDL (期待値)

差分ルールは HistoryHTML と同じです。例えば、"カラム定義順序の違い" や "同じ構造で制約名だけの変更" など、基本的にアプリに影響のない微修正ものに関しては差として認識されません。(一部、DBFluteプロパティでルールを変更できる)

また、CraftDiffを利用している場合は、その差分も AlterCheck でチェックされます。例えば、区分値テーブルなどの insert 処理の書き忘れが検知されます。 (CraftDiffのTSVファイルは、playsql/migration/schema/craftdiff 配下に出力される)

3. 自動配置されたSQLファイルの確認 (alterディレクトリ)

この時点で、playsql/migration配下に alter というディレクトリが自動で作成され、AlterDDLを記述するための alter-schema.sql が自動配置されています。

そのファイルの中身は、同じスプリント内で最初のDB変更か?二回目のDB変更か?によって変わります。

スプリント内で最初のDB変更
alter-schema.sql は空っぽ
すでに別の人がDB変更
alter-schema.sql に別の人のDB変更のDDLが書かれている
e.g. alter の alter-schema.sql (AlterDDL) の自動配置 @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema.sql // 空っぽ or 別の人のDDLが書かれている
    |  |-previous
    |  |-...

もし、同じスプリント内ですでに別の人がDB変更をしていたら、その内容が復元されます。 SQLファイルは分割できるので複数ファイルが展開される可能性もあります。

e.g. alter-schema-[チケット番号].sql 形式で、別の人のSQLファイルが復元された場合 @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema-SEA.sql // 別の人のDDL, チケット番号SEA用
    |  |  |-alter-schema-LAND.sql // 別の人のDDL, チケット番号LAND用
    |  |-previous
    |  |-...

別の人の DDL も確認した上で自分の DDL を書かないと矛盾が発生する可能性もありますので、 "周りはこんなDB変更してるんだぁ" というのを軽くでも把握して整合性を保つことは大切です。

一方で、別の人の DDL を修正するケースや、自分が前にチェックした DDL をもう一度修正するようなケースでは、その復元された DDL を修正することになります。

4. SQLファイルの粒度の調整 (alterディレクトリ)

SQLファイルをどういう粒度で作るかは現場のルールによって違いますので確認をしましょう。

ひとつのファイルに集約パターン

自動で作成もしくは復元されている alter-schema.sql にそのまま追記していきましょう。

e.g. ひとつのファイルに集約パターン @playsql
...
    |-migration
    |  |-alter
    |  |  |-alter-schema.sql // ここに追記
    |  |-...

チケットごとにのファイル分割パターン

空っぽの alter-schema.sql がある場合
そのファイルを alter-schema-[チケット番号].sql に変更して、そこにDDLを書きましょう。
別の人のSQLファイルだけが復元されている場合
新しいSQLファイルを手動作成して、そこにDDLを書きましょう
e.g. alter-schema-[チケット番号].sql 形式で、別の人のSQLファイルが復元された場合 @playsql
...
    |-migration
    |  |-alter
    |  |  |-alter-schema-SEA.sql // 別の人のDDL, チケット番号SEA用
    |  |  |-alter-schema-LAND.sql // 別の人のDDL, チケット番号LAND用
    |  |  |-...
    |  |  |-alter-schema-PIARI.sql // ここに追記 (手動作成)
    |  |-...

5. さあ、alter文を書いてチェック (alterディレクトリ)

該当のSQLファイルに、自分のDB変更分のalter文を書きましょう。

エンコーディング
UTF-8
SQLのデリミタ
セミコロン
文字列リテラルの改行
LFで登録される

DDLだけじゃなく、そのDDLを実行するために必要な insert/update/delete などの DML も書くことができます。 例えば、NotNull制約を付けるための update を一緒に書くことができます。

さて、AlterCheckタスクを再び実行してみましょう!

もし、記述した AlterDDL に不整合(食い違い)がある場合は、AlterCheckタスクは中断され、その差分が先ほどの alter-check-result.html に出力されます。差分がなくなるまで AlterDDL を修正してはチェックしてを繰り返します。

(手動で書いている)AlterDDLが文法上のミスなどでエラーになる可能性も十分ありますので、中断された場合はログのメッセージをしっかり読みましょう。

6. 差分がなくなったら成功 (historyディレクトリ)

差分がなくなったらAlterCheckタスクは正常終了し、その AlterDDL は妥当性のチェック済みとして migration/history 配下に保存されます。alter配下から移動されるので、alterディレクトリは空っぽになります。

DBFlute-1.2.1 からは unreleased-checked-alter

チェック成功した AlterDDL のSQLファイルは、migration/history/unreleased-checked-alter ディレクトリに保存されています。 ここは本番DBへの反映を待つ場所なので、直接手動で修正をしてはいけません。

e.g. AlterDDLが保存されるHistoryのunreleased-checked-alter @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-alter // SQL files was moved to history
    |  |-history
    |  |  |-unreleased-checked-alter
    |  |     |-DONT_EDIT_HERE.dfmark
    |  |     |-for-previous-20120804-1746.dfmark
    |  |     |-READONLY_alter-schema.sql
    |  |     |-READONLY_alter-schema-[チケット番号].sql
    |  |-previous
    |  |-schema

リリースが終わって次のスプリントのSavePreviosが実行されるまでは、そのスプリントのすべてのDB変更のAlterDDLが unreleased-checked-alter に保存されています。本番DBにリリース作業をする人は、このディレクトリのSQLファイルを参照します。

DBFlute-1.2.0 までは ZIPファイルにまとめられる

直近の日付/日時のディレクトリに入っている checked-alter...zip が最新の AlterDDL です。

e.g. AlterDDLが保存されるHistory @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-alter // SQL files was moved to history
    |  |-previous
    |  |-history
    |  |  |-201208
    |  |     |-20120806_1023 // saved at 2012/08/06 10:23
    |  |        |-checked-alter-to-20120804-1746.zip // for 2012/08/04 17:46
    |  |-schema

リリースが終わって次のスプリントのSavePreviosが実行されるまでは、そのスプリントのすべてのDB変更のAlterDDLが checked-alter の ZIP に保存されています。本番DBにリリース作業をする人は、このZIPのSQLファイルを参照します。

(7. チェック成功後にAlterDDLを修正するなら)

"DB変更自体の手直し" のケースと、"AlterDDLのちょこっと修正" のケースで少し分かれます。

DB変更自体の手直し (NextDDLもAlterDDLも修正)

このページの手順の "0. DB変更でNextDDLを更新" を繰り返します。 新しいSQLファイルを作る必要はないので、AlterCheck後に復元されたSQLファイルを修正してAlterCheckしましょう。

ただし、AlterCheckの差分が出ないレベルの手直し (例えばDBコメントのちょい修正やFK制約名の修正など) は、AlterCheckを実行しても差分にならずに成功してしまい、前に書いたSQLファイルがalterディレクトリに復元されません。

なので、その場合は unreleased-checked-alter ディレクトリ配下のSQLファイルを手動で (READONLY_のprefixを除去して) alterディレクトリにコピーして修正して AlterCheck しましょう。 (ちなみに、DBFlute Introであれば、AlterCheck実行せずに復元されるので、このような手間は発生しません)

AlterDDLのちょこっと修正 (AlterDDLのみ修正)

DB変更自体(NextDDL)はそのままでAlterDDLの書き方やSQLコメントの修正だけのときも、unreleased-checked-alter配下のSQLファイルを手動で (READONLY_のprefixを除去して) alterディレクトリにコピーして修正して AlterCheck しましょう。 (ちなみに、DBFlute Introであれば、AlterCheck実行せずに復元されるので、このような手間は発生しません)

差分に関係のない修正だとしても、ミスや勘違いも十分あり得ますので、unreleased-checked-alterを直接修正せずに、必ず AlterCheck のオペレーションを経由しましょう。 (ミスや勘違いで本番DBのメンテナンスでトラブルになるよりかは、いまここで少しの手間をかけたほうが良いです)

やはり DBFlute Intro で作業するのがオススメ

こういった細かいユースケースへの対応も、DBFlute Intro経由でのオペレーションの方が安全にできますので、特別な理由がない限りは DBFlute Intro を使うほうが良いでしょう。

(8. 正常終了後のDBの状態)

AlterCheck正常終了後のDBは、PreviousDB に AlterDDL を適用させたものとなっています。 そのままアプリを起動してテストを行えば、より本番に近い状態のテスト環境として利用できます。

そもそもDDLがNGだったら

PreviousNGマーク

そもそも PreviousDDL が文法的に間違っていて実行時にエラーとなった場合、および、不整合があって差分が発生した場合、 previous-NG.dfmark が作成されます。再実行で SavePrevious が正常に終了したら自動的に削除されます。

e.g. PreviousNGマーク @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-previous-NG.dfmark

AlterNGマーク

そもそも AlterDDL が文法的に間違っていて実行時にエラーとなった場合、および、不整合があって差分が発生した場合、 migration 配下に alter-NG.dfmark というファイルが作成されます。これは単に AlterDDL がダメだったよ、ということをおおげさに表現するマークファイルです。AlterCheck が成功したら自動的に削除されます。

e.g. AlterNGマーク @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema-10-basic.sql
    |  |  |-alter-schema-20-view.sql
    |  |-alter-NG.dfmark

NextNGマーク

そもそも AlterCheck 実行時の最新状態、つまり playsql 直下のDDLやデータが文法的に間違っていて実行時にエラーとなる場合、 migration 配下に next-NG.dfmark というファイルが作成されます。これは単に NextDDL がダメだったよ、ということをおおげさに表現するマークファイルです。AlterCheck が成功したら自動的に削除されます。

e.g. NextNGマーク @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema-10-basic.sql
    |  |  |-alter-schema-20-view.sql
    |  |-next-NG.dfmark

NGマークは NG の強調

そもそもNGマークの役割についてですが、単なる NG であることの強調という意味に尽きます。

しっかりとコンソールや dbflute.log でエラーメッセージを見てアクションを取って欲しいのですが、 あまりコンソールを見ずに(エラーに気付かずに)先に進んでしまうこともありえるので、おおげさにマークファイルを出力しています。

AlterDDL で外部スクリプト

AlterDDL において、SQLの実行だけでなく外部スクリプトを実行することができます。 DBへの差分反映は、単純なSQLで済まされない場合もあります。DBMSのコマンドを利用して特殊な操作をすることもあるでしょう。 そういう場合のために、外部スクリプトが実行できるようになっています。

AlterDDL のSQLファイルと同じディレクトリに、alter-schema で始まる拡張子が .bat もしくは .sh のスクリプトファイルを配置します。SQLファイルも含めてファイル名の昇順で実行されます。

e.g. AlterDDL の外部スクリプトの配置 @playsql
dbflute_maihamadb
 |-playsql
    |-data
    |-migration
    |  |-alter
    |  |  |-alter-schema-10-basic.sql
    |  |  |-alter-schema-20-view.sql
    |  |  |-alter-schema-30-special.sh

スクリプトの exitCode が 0 以外の場合は、スクリプト実行エラーとみなされます。

スクリプトのコンソールログは、固定で UTF-8 として取得され、DBFluteタスクのログと一緒に出力されます。なので場合によっては、ログが文字化けする可能性はありますが、処理に影響はありません。

AlterCheckの雑多な補足

そもそもいま本番とズレてない?

一番最初の導入したての AlterCheck のとき、そもそも現時点で本番DBと開発DBがズレてしまっていては、いくら AlterCheck をしてもズレてる状態をキープし続けるだけです。SchemaSyncCheck を使ってチェックしてズレがあれば直すと良いでしょう。

さて、本番DBと開発DBにズレがなくなれば、ここからが AlterCheck の活躍の場面です。

あえて AlterDDL はベタに

AlterDDL の実装方法として、SQLファイルと外部スクリプトというベタな方式に絞っているのは、本番環境への適用を考えてのことです。 本番環境のDBは、多くの場合開発者の手の届かないところにあります。 また、DBFluteを本番環境のDBが見える場所に配置して直接接続できるというようなことも少ないでしょう。 それはセキュリティのことを考えると当然のこととも言えます。

というわけで、AlterCheck の時点で DBFlute に依存した形式でチェックをしてもあまり意味がないとも言えます。 例えば、DB変更の一貫として必要となるデータをエクセルデータやTSVデータなどで管理して DBFlute の機能で登録しても(そういった機能は実装可能ですが)、本番環境では別の方法(DBMS のコマンドなど)で追加する必要があるかもしれません。

そういうことから、あえて AlterDDL はベタに実装することを想定した仕様にしています。 ただ、この本番環境適用は、そもそも多種多様でまだまだ分析の余地のある領域でもあるため、 今後の分析次第ではまた違った解釈で色々なアプローチを検討していくかもしれません。(2011/05/01現在)

DDLのDIFFにも利用できる

AlterCheck は本来の目的とは別に、単に 二つのDDLの構造の差を確かめる する用途にも利用できます。例えば、同じスキーマを作成する手動で作成されたDDLとツールで自動生成されたDDL、 これらが同じ結果をもたらすかどうか確認できます。

0.9.9.7B, 0.9.9.7A より前では...

0.9.9.7B より前のバージョンでは、Manageタスクでの実行ではなく、migraton配下に save-previous.dfmark というファイル(中身は空でよい)を作成して、ReplaceSchemaタスクを実行すると SavePrevious が実行されました。 まあ、alter配下に AlterDDL を配置した状態でReplaceSchemaタスクを実行すると AlterCheck が実行されました。

AlterCheckは、ReplaceSchemaの仕組みを利用していますが、ReplaceSchemaとは目的も運用タイミングも違うので、 明示的に別のタスクとして独立しました。

また、0.9.9.7A より前のバージョンでは、ZIP形式ではなく一つ一つのファイルがそのまま保存されます。 新しいバージョンでも "ZIPがなければ一つ一つのファイルを探す" 仕様になっているので、スムーズなバージョン移行は可能です。