増田亨さんによる「設計の考え方とやり方」勉強会 書き起こし3ページ目です。最初からお読み頂く場合は、こちらから御覧ください。
資料
書き起こしリンク
- パート1「良い設計を目指す」
- パート2「設計スタイルの選択とクラス設計のスタイル」
- パート3「テーブル設計のスタイル」(本記事)
- パート4「開発のやり方と設計スキルと補足資料」
- パート5「質疑応答」
目次
2022/08/24 追記
イミュータブルデータモデルについてより詳し知りたい方は、WEB+DB Press Vol.130 も是非お読みくださいませ!
パート3の内容(イミュータブルデータモデル)については、WEB+DB Press 最新号(Vol.130)に、@kawasima さんのすばらしい記事があります。こちらの記事ぜひ読んでください。https://t.co/rwBSL67cX6 https://t.co/N8s6JAKaIG
— 増田 亨. (@masuda220) 2022年8月24日
テーブル設計のスタイル
テーブル設計の分かれ道
こんどはクラスではなくて、テーブル設計の話です。
テーブル設計も、二つスタイルがあって、一つはミュータブルな設計、つまりUPDATE文を使うことが当たり前なデータモデルでテーブル設計する。もう一つはイミュータブルな追記だけでテーブル設計をするパターンです。
どちらを選ぶかは、しっかりと意識したほうがいいです。設計が全然違ってきてしまう。テーブルだけではなくて、SQL文とかクラス設計も変わってくる。どちらでやるのかは重要なポイントです。
イミュータブルモデルを選ぶ
私自身はイミュータブルデータモデルに振り切って取り組んでいます。最初はやっぱりチャレンジでした。うまくいかないこともあったんですけれども、最近は本当にイミュータブルに振り切れば振り切るほどうまくいくんじゃないかという感じで取り組んでいます。
事実の記録を徹底することが、データベースの第一義的な目標ですよね。実際に起きたことをきちんと記録しておく。起きた事実は消しません。上書き変更はしないんです。一度起きた事実は、ビジネスにとってはいろんな意味で証拠であり、参照すべき情報です。事実の記録は消さない、上書きしないのが基本です。事実の記録があれば「今の状態ってどうなっているんだ?」という時に、事実の記録を再生すれば現在の状態が導出できるという仕組みになります。まず、事実の記録が命。
イミュータブル方式でテーブル設計をやるようになって実感できたことなんですけど、上書き更新文、UPDATE文はやっぱりデータ操作が複雑。「このテーブルを使う時に何がどういうタイミングでどう変わる可能性があるか」みたいなことを考慮したり、あるいはそういうことの考慮漏れでSELECT文のWHERE句の記述ミスで痛い目にあったりとか、そういうことがけっこう多かったんです。
イミュータブルデータ方式にするとUPDATE文の複雑さとか嫌らしさ、状態判定、SQL文のWHERE句とか、プログラムの中のif文とか、だいぶスッキリします。 そういう効果もあるし、事実の記録が残っているという絶対的な安心感がある。これが凄くいいなと思って、今はやっています。
イミュータブルデータモデルの効果
イミュータブル方式にすると、まず挙動が安定するんですよね。同じ条件で参照すれば必ず同じデータが取得できるし、書き込みも単純にINSERT ONLYだけで、新しい事実が起きたらとにかくにINSERTしていけばよい。
変更やキャンセルが発生した場合は、何に対する変更だったかということも含めて事実として記録しなきゃいけないんで、解像度を少し上げたINSERTが必要になるという側面があるんですけど。いずれにしてもINSERT ONLYであれば競合は起きないし、書き込みは単純になります。
読み取りも単純になります。必要な事実ごとにテーブルが出来ているんで、ある事実、ある条件に合致したデータは取りやすい。 たとえば注文一覧のデータを取りたいといった時に、いろいろなテーブルをジョインして、区分とかそういうもの、WHERE句でもand ,and ,or ,and, or…みたいなことをしなくてもいい。「出荷指示がまだ済んでいない注文はどれですか?」みたいな問い合わせに関して、WHERE句抜きで、あるテーブル全件取ってくればいいみたいな、そういう単純さが実現できます。
あとは、ドメインモデル方式と非常に相性がいいんです。ビジネスルールは発生した事実を使っての計算判断なので、同じ事実があった場合、同じビジネスルールを適用したら同じ結果にならないといけません。計算する度に割引金額が変わってしまう、みたいなことはあってはいけないことなんで、ある物をある条件で買っていただいたら必ず同じ割引金額になります、みたいなことを実現しようとした時に、「テーブルの内容は絶対変更されていません」「正しい事実だけちゃんとセレクトして集めてくれば必ず同じ結果を担保できます、保証できます」というようなテーブル設計になっていると、ドメインモデル方式と非常に相性がいいんです。
今後の潮流という背景の一つでもあるんですけど、分散システムとの相性がいいんですね。同じ事実の不変なデータであれば、どこにどう複製しても不整合が起きない。事実の記録は変わらないわけだから、同じ事実はどんどん複製して、どこでいつどう参照しても基本的には不整合は起きません。
強いて言えば、複製する微妙なタイミングの差で、データベースのAではインサートは終わっているけど、データベースのBでは複製が終わっていない、という状態は瞬間的にはあります。でもそれはほんのちょっとした時間のずれだけで、必ず同じ事実をセットとして整合が取れます。いわゆる結果整合性の一種です。そうした時に分散システムと非常に相性がいいということです。
上書き更新イベントとか、上書きして更新される状態を分散システム間で正確に伝播して同期させる、不整合を起こさないような仕組みを考える、本当に不整合を起こさないことをリアルに実現するということは相当難易度が高いです。これはもうマイクロサービスに取り組んだり、その方面のことをいろいろ調べたりチャレンジされている方はよくご存知だと思いますけど、あり得ないんですね。分散環境で完全にトランザクションで整合性を保証するみたいなことは。
このあたりが今後、システムが分散していくという、いろいろな分散システムが動的につながったり離れたりしながら、大きな処理をしていくという方向はもう間違いないんです。
そうなった時に、イミュータブルにデータを扱う考え方は、取り組むんであれば早くから取り組んで、ノウハウを貯めながら、自分たちが普通に使える、普段使いできる道具として、ぜひイミュータブル方式によるテーブル設計あるいはデータベースの構築をやっていっていただければと思っています。
イミュータブルに設計したテーブルの特徴
ミュータブルなテーブル設計だと、たとえばNOT NULL制約が難しいカラムも出てきてしまう。あとから更新する予定のカラムとかですね。イミュータブルなテーブル設計にして、事実を記録するだけであれば、発生した事実の記録ですから、NULLはそもそもあり得ない。NULLという事実はありません。あとは外部キー制約、イベントとあるイベントの関係を全部外部キーで結んでおくことで、事実の前後関係をトレースしやすくなります。
あと、1テーブルのカラム数は減るんですね。一つのテーブルには発生時点が同じカラムしか書きませんから、「この時はこういう情報とこういう情報が一緒に発生しているけど、こういうケースではこういう情報しか発生しません」というケースがあった場合に、後者の「こういう情報だけしか発生しない」用のテーブルを作るというのが基本的な考え方なので。一つのテーブルには発生時点が同じカラムだけというふうに指針を決めてしまえば、カラムは自然に少なくなります。
必須の情報だけしか書けなくなる。任意の情報は、別のテーブルにINSERTする。必須の情報だけのテーブルという方針を徹底していくほど、一つのテーブルのカラム数は減っていきます。状態を上書き記録するカラムがなくなると、更新プログラムIDと更新タイムスタンプというカラムが意味がないというか、そもそも作るべきではないカラムになってしまう。
プログラムが単純かつ明快になる
SQLも単純になるし、SQLだけではなくてデータベースプログラムも単純になります。ドメインオブジェクトと事実を記録したテーブルマッピングも単純になる。実際にやってみていただくとわかると思います。
テーブル更新(状態変化)を前提にした、ある意味防御的なプログラミングというか、「こういう状態の場合は駄目だけどこういう状態の場合はOK」とか、「こういう状態とこういう状態が組み合わさった時だけここのカラムの値を使っていい」みたいな複雑な記述はほぼなくなるので、状態に依存したWHERE句とかCASE式とか、プログラムのほうであればif文とかswitch文とかは激減します。
パート3は以上です。パート4の「開発のやり方と設計スキルと補足資料」はこちら。
asken では、サーバーサイドエンジニアを大募集しています!