現場で役立つシステム設計の原則 ch6 データベースの設計とドメインオブジェクト
テーブル設計が悪いとプログラムの変更が大変になる
データの整理に失敗しているデータベース
- データベース設計がまずいと、データを適切に記録するためにテーブル定義やデータ内容に表れない暗黙知が必要となる
- まずい設計例
- 用途がわかりにくいカラム
- いろいろな用途に使う巨大なテーブル
- テーブルの関連がわかりにくい
- 【補】リレーショナルモデルを勉強しろ、に尽きる
データベース設計をすっきりさせる
基本的な工夫を丁寧に実践する
- 名前を省略しない
- テーブル名やカラム名の文字数に厳しい制限があったのも今は昔
- 適切なデータ型を使う
- 大は小を兼ねる、はNG
- 無駄に大きな桁数
- TEXTやLOB
- データのドメインを適切に縛ることで不正データを防ぐ
- 大は小を兼ねる、はNG
- 制約をきちんと使う
- NOT NULL
- UNIQUE
- FK
NOT NULL制約が導くテーブル設計
- 【補】そもそもNULLが入った時点で第1正規形ですらない
- 第1正規形: 「テーブルがリレーションである」
- 必要条件
- タプル(行)のアトリビュート(列)の値がアトミックである
- 簡単には1カラム1つの値、的な意味
- NULLがない
- NULLはUNKNOWNまたはNOT APPLICABLE
- 「1カラム1つの値」に反する
- ...
- タプル(行)のアトリビュート(列)の値がアトミックである
- 必要条件
- 第1正規形: 「テーブルがリレーションである」
- 【補】リレーショナルモデルを勉強しろ(再掲)
- 【補】
accounts
テーブルにdeleted_at
(nullable)カラムを定義するのではなく、deleted_accounts
テーブルを定義するのがリレーショナルモデルとして正しいあり方
一意性制約でデータの重複を防ぐ
- 重複は異常(anomaly)につながる
- これを防ぐ
外部キー制約でテーブル間の関係を明確にする
- NOT NULL制約と一意性制約を徹底し、正規化していくと、テーブルは小さく多くなる
- ここにおいて、テーブル間の関係を明確にするために外部キー制約が重要
コトに注目するデータベース設計
業務アプリケーションの中核の関心事は「コト」の管理
- 4章再訪
- 業務アプリケーションにおいては、コトを正しく記録し参照するためにデータベースが重要
- 現実に起きたコトの記録
- 将来起きるコトの記録
- 制約とコト
- NOT NULL制約
- 起きたコト(事実。真となる命題)を記録するのにNULLは不適
- だってNULLの意味するところはUNKNOWNですから
- 起きたコト(事実。真となる命題)を記録するのにNULLは不適
- 一意性制約
- 参照時に苦しまない
- 外部キー制約
- JOINして正しいデータを再現
- 【補】結合従属性
- JOINして正しいデータを再現
- NOT NULL制約
ヒトやモノとの関係を正確に記録するための3つの工夫
- 記録のタイミングが異なるデータはテーブルを分ける
- さもないとNULLが生じる
- 「未定」
- さもないとNULLが生じる
- 記録の変更を禁止する
- 取り消しデータと新データを登録する
- 会計と同じ
- 取り消しデータと新データを登録する
- カラムの追加はテーブルを追加する
- 既存テーブルへのカラム追加は、NULLまたはNULL逃れの虚のデータを生む
- 新しくテーブルを追加し、新しいテーブルから既存のテーブルを外部キーで指す
- 【補】スタースキーマ的な
参照をわかりやすくする工夫
コトの記録に注力したテーブル設計の問題
- 多くのテーブルを結合する必要が出てくる
- 導出ロジックの複雑化
- 性能面の問題
- 【補】RDBMSはせいぜい4-5個テーブルのJOINまでを前提に設計されている
状態の参照
- コトの記録だけで現在の状態は導出可能
- 現在の状態を参照しやすくするための冗長化は、データの重複や不正なデータの混入の温床
- とはいえ毎回導出するのは前述の問題がある
- どうする?
- 参照用の導出データも用意する
- 検索におけるインデックスと同様
- コトの記録のたびに更新
- あくまで原本は正規化されたデータ
- 検索におけるインデックスと同様
UPDATE文は使わない
- UPDATE文はデータの不整合が混入しやすい
- 記録の同時性に違反
- 【補】UPDATE前提のNULLカラムとかが生まれる
- 記録の同時性に違反
- 残高(状態)の更新には、代わりにDELETE/INSERTを使う
- 副産物: 冪等
- DELETE/INSERTではすでにレコードがあってもなくても操作できる
- cf. UPDATE方式は、新規登録時は代わりにINSERTする必要がある
残高更新は同時でなくてもよい
- コトの記録と残高(状態)の更新は厳密なトランザクションとして処理されるべきではない
- 厳密に同時である必要なし
- 【補】結果整合性の話をしている??
- 数ミリ秒遅れても問題ないこと多し(要件依存)
- 二次的な「残高」の更新に失敗したからといって、本質である「コト」の記録も取り消すのはおかしい
- 厳密に同時である必要なし
- 同時でなくていいので、非同期メッセージングの可能性が出てくる
- システムの設計をシンプルにできる
残高更新は1ヵ所でなくてもよい/派生的な情報を転記して作成する
- 残高更新を複数のサーバで別々に行っても良い
- PUB/SUB
- イベントソーシング
- コトの記録を情報源として、派生するさまざまな情報を目的別に記録する
- メリデメ
- メリ
- 参照系がシンプルになる
- 目的ごとに柔軟に修正や拡張ができる
- デメ
- 厳密な即時性、派生データ間の整合性の保証には相応の仕組みが必要
- メリ
コトの記録から状態を動的に導出する
- 副産物的なうれしいこと
- 将来のコトを擬似的に発生させることで、未来状態のシミュレーションにも使える
- 過去のある時点での状況を擬似的に作成し、テスト環境として利用できる
オブジェクトの設計とテーブルの設計
オブジェクトとテーブルは似ている
- 似てるだけ
違うものとして明示的にマッピングする
特性 | オブジェクト | テーブル |
---|---|---|
目的 | データとロジックの整理 | データの整理 |
関心事 | 導出や加工、判断 | 導出や加工の元となるデータ |
アプローチ | ボトムアップ(部品を組み立てる) | トップダウン(整合性のため) |
設計変更のリズム | 頻繁 | ゆるやか |