🔖

migration、fixtureと開発時の起動スクリプトに関する私見

2021/02/01に公開

migration と fixture を開発時の起動スクリプト内に書いて毎回起動するべか?という話題について個人的に考えたことをまとめたものです。
なので、一般に言われている知識とかではないです。コメント欄で「こう思うとか」とか「あの人はこう言ってるよ」とかなんでも意見もらえるとできると嬉しいです。

(migration と fixture というのは Python Django のもの、開発時の起動スクリプトというのは docker-compose up を想定しています。)

migration と fixture について

migration って何?

migration は「移行」とかいう意味の英単語で、ソフトウェア開発においては、DB スキーマの変更と、変更を実現する(大抵フレームワークによって用意された)手法のこと。

Django のプロジェクトでは

python manage.py migrate --noinput

というコマンドを起動前に実行するようにしていたりする。
このコマンドは./{プロジェクト名}/migrations/ 以下にあるファイルたちに書かれた情報と、今接続されているデータベース内の django_migrations というテーブル内のデータを元に
自動的に今接続されているデータベースのテーブルスキーマを変更し、./{プロジェクト名}/migrations/ に書かれた最後の状態のものにしてしまう。

この migrate という操作は django に限らず、web フレームワークにはよくあるものだったりする。
なんのためにある、どんな仕組みのものなの?というのはちょっと引用。

Rails ガイド での説明のされ方は

マイグレーションは、データベーススキーマの継続的な変更 (英語) を、統一的かつ簡単に行なうための便利な手法です。

とのことで、今回の話題は Wikipedia 内のバージョン管理システムとの関係(英語)の以下の部分と関わりが深い。

  • ソースコードのすべてのバージョンは、少なくとも 1 つのデータベーススキーマに関連付けることができます。
  • スキーマ移行ツールは通常テストの前提条件としてのビルドの一部として呼び出されます。
  • このアプローチにより、特定のコードブランチと互換性のあるデータベーススキーマを再現するために必要な情報がソースツリー自体から回復可能になります。
  • このアプローチのもう 1 つの利点は、競合するスキーマの同時変更を処理できることです。開発者は、通常のテキストベースの競合解決ツールを使用して、違いを調整することができます。

という風に書いてある。

fixture って何?

fixture は「備品」とかいう意味の英単語で、ソフトウェア開発においては、テストを毎回同じ条件で実行するための事前準備を提供するもののこと。

一般的にはデータベース内のデータに限らないけど、Django においては fixture といえばデータベースのデータを提供する機能。

python manage.py loaddata ./{プロジェクト名}/fixtures/*.json

というコマンドを実行することで ./{プロジェクト名}/fixtures/*.json に書かれたデータを接続されているデータベースに挿入することができる。

Rails においては

  • 基本となるデータセット(結構でかい DB データそのままのダンプとかも想定)を入れる場合
  • あるテストが動けば良いというだけの小さいデータセットを入れる場合

で別の仕組みが用意されている(前者が seed, 後者が fixture)が、Django の場合公式にあるのは fixture のみで前者も fixture でまかなうことが多い。
(あまりにも大きいデータの場合はそもそも git 管理しにくいし、データベース自身の機能であるダンプとリストアを利用すると良さそう。)

migration と fixture についての理想像

というところで、migration と fixture の運用方法の理想像を考えました。

  • 開発の起点となるブランチの全てコミットは、そのコミットにおいてゼロから migration(スキーマ)と fixture(データ)を使って皆同じ状態で開発を始めることができる。
  • DB のスキーマが違う状態で始めたい時はまずその migration を追加したコミットを作って(ブランチを切ったりして)行う。そのコミットにおける最新の migration と違うスキーマで開発することを許容しない。
  • データに関しては複数の状態を一つのコミット(スキーマ)で確認できるようにして良い。(つまり fixture は複数種類あって使い分けられる。)<-「このデータがないときはこういう挙動」で、「ある時はこういう挙動」とか確認したいから。
  • マージやリバート時にはコードのコンフリクトを矛盾なく解消するのと同様に、DB スキーマが矛盾を引き起こさないことも確認・矛盾があれば解消する必要がある。
    • 普通はテストを書く。テストは複数種類の fixture を利用しながら行うことができる。スキーマは複数種類確認する必要はなく、そのコミットにおいて最新のもののみテストすれば良い。

前述の理想を守ると起きる事

  • レビュー時に常に実装者と同じ状態で動作確認を行うことができる。
  • ネカフェなどで毎日違う PC で開発することになっても、開発者が総入れ替えになっても問題が起きず、問題発生時にもいつの commit にでもロールバックすることができる。
  • commit=DB スキーマなので、意図しない migrate が起こる=もってきた commit が間違ってるで、と言うことになる。

これは厳しい理想で、もちろん手でちょろっと変更を加えたりして commit が指定する状態とは違うもので開発を進める場合も多々あって当然。
ただ、そこで加えた変更というのは今の自分以外誰にもわからないので、いわゆるサポート外としてやってもらうべきかなと思う。
他の人のレビューやサポートが欲しければ、それを表す migration や fixture を書いてくださいということ。
(それでも開発速度を優先させるために migration, fixture なしで回避策みたいなのを用意してやっていくのは小さい規模だったり、一時的なものであることがわかっている部分ならアリかもだけど、一般的には関わる人数が増えたり開発が進んでいくうちに逆にどんどん時間がかかるようになっていってしまう。)

デフォルトの開発時の起動スクリプトはどうあるべきか

これは前述の理想論に比べるとどちらでも良いという感じがするんだけど、二つの選択肢を比べて考えてみる。
ここでは loaddata fixture は複数種類あるうちのみんなが必要とする基本データが読み込まれるものとする。

選択肢 1

migrateloaddata fixturedocker-compose up では起きないようにし、README に、

  • (開発始めやレビューなどで) commit を移動した際には、まず migrateloaddata fixture をしてください。(あるいはそれが起こるスクリプトを起動してください。)
  • その後開発・動作確認には docker-compose up をしてください。

と書く。

選択肢 2

migrate と loaddata fixturedocker-compose up起きるようにし、README に、

  • 開発・動作確認には docker-compose up をしてください。

と書く。

選択肢 1 の利点・欠点

  • 利点:自分の DB に変更を加えている人には変更を勝手に加えられることがなく安心。(<-上記理想論でいうところサポート外の人が嬉しい)
  • 欠点:気にしなければならない操作が増える。(これは特に新規さん、デザイナーさんとかを罠にかけその人やサポートする人の時間を消費しそう。)

選択肢 2 の利点・欠点

選択肢 1 の逆で

  • 利点:気にしなければならない操作が減る。
  • 欠点:自分の DB に変更を加えている人にとって意図しない DB の改変が行われる。

あと、毎度起動するとは言っても 2 回目以降はスキップできているところがほとんどなので、起動速度はあまり変わらないはず。

ということで、選択肢 2 が良さそうな感じがする。

まとめ

  • 開発規模が大きくなっていくのなら手動更新はやめ、migration, fixture, テストを書きましょう。
  • migrateloaddata fixture (の内のみんなが必要とする基本データ)は開発者用の docker-compose up では起きるようにする。
  • 変更させたくない人用に migrateloaddata fixture が起動しない特別な docker-compose.yml を用意して docker-compose up -f ファイル名
    で起動してもらうなどして、回避策を用意する。
    • 詳しくない人・多数向けには全く説明のいらない方の操作をしてもらうことにし、詳しい人・少数向けには少し説明のいる方の操作をしてもらうことにする方針。

Discussion