勉強日記

チラ裏

現場で役立つシステム設計の原則 ch6 データベースの設計とドメインオブジェクト

gihyo.jp


テーブル設計が悪いとプログラムの変更が大変になる

データの整理に失敗しているデータベース

  • データベース設計がまずいと、データを適切に記録するためにテーブル定義やデータ内容に表れない暗黙知が必要となる
  • まずい設計例
    • 用途がわかりにくいカラム
    • いろいろな用途に使う巨大なテーブル
    • テーブルの関連がわかりにくい
  • 【補】リレーショナルモデルを勉強しろ、に尽きる

データベース設計をすっきりさせる

基本的な工夫を丁寧に実践する

  • 名前を省略しない
    • テーブル名やカラム名の文字数に厳しい制限があったのも今は昔
  • 適切なデータ型を使う
    • 大は小を兼ねる、はNG
      • 無駄に大きな桁数
      • TEXTやLOB
    • データのドメインを適切に縛ることで不正データを防ぐ
  • 制約をきちんと使う
    • NOT NULL
    • UNIQUE
    • FK

NOT NULL制約が導くテーブル設計

  • 【補】そもそもNULLが入った時点で第1正規形ですらない
    • 第1正規形: 「テーブルがリレーションである」
      • 必要条件
        • タプル(行)のアトリビュート(列)の値がアトミックである
          • 簡単には1カラム1つの値、的な意味
        • NULLがない
          • NULLはUNKNOWNまたはNOT APPLICABLE
          • 「1カラム1つの値」に反する
        • ...
  • 【補】リレーショナルモデルを勉強しろ(再掲)
  • 【補】accountsテーブルにdeleted_at (nullable)カラムを定義するのではなく、deleted_accountsテーブルを定義するのがリレーショナルモデルとして正しいあり方

一意性制約でデータの重複を防ぐ

  • 重複は異常(anomaly)につながる
  • これを防ぐ

外部キー制約でテーブル間の関係を明確にする

  • NOT NULL制約と一意性制約を徹底し、正規化していくと、テーブルは小さく多くなる
  • ここにおいて、テーブル間の関係を明確にするために外部キー制約が重要

コトに注目するデータベース設計

業務アプリケーションの中核の関心事は「コト」の管理

  • 4章再訪
  • 業務アプリケーションにおいては、コトを正しく記録し参照するためにデータベースが重要
    • 現実に起きたコトの記録
    • 将来起きるコトの記録
  • 制約とコト
    • NOT NULL制約
      • 起きたコト(事実。真となる命題)を記録するのにNULLは不適
        • だってNULLの意味するところはUNKNOWNですから
    • 一意性制約
      • 参照時に苦しまない
    • 外部キー制約
      • JOINして正しいデータを再現
        • 【補】結合従属性

ヒトやモノとの関係を正確に記録するための3つの工夫

  • 記録のタイミングが異なるデータはテーブルを分ける
    • さもないとNULLが生じる
      • 「未定」
  • 記録の変更を禁止する
    • 取り消しデータと新データを登録する
      • 会計と同じ
  • カラムの追加はテーブルを追加する
    • 既存テーブルへのカラム追加は、NULLまたはNULL逃れの虚のデータを生む
    • 新しくテーブルを追加し、新しいテーブルから既存のテーブルを外部キーで指す

参照をわかりやすくする工夫

コトの記録に注力したテーブル設計の問題

  • 多くのテーブルを結合する必要が出てくる
    • 導出ロジックの複雑化
    • 性能面の問題
      • 【補】RDBMSはせいぜい4-5個テーブルのJOINまでを前提に設計されている

状態の参照

  • コトの記録だけで現在の状態は導出可能
    • 現在の状態を参照しやすくするための冗長化は、データの重複や不正なデータの混入の温床
  • とはいえ毎回導出するのは前述の問題がある
  • どうする?
  • 参照用の導出データ用意する
    • 検索におけるインデックスと同様
      • コトの記録のたびに更新
      • あくまで原本は正規化されたデータ

UPDATE文は使わない

  • UPDATE文はデータの不整合が混入しやすい
    • 記録の同時性に違反
      • 【補】UPDATE前提のNULLカラムとかが生まれる
  • 残高(状態)の更新には、代わりにDELETE/INSERTを使う
  • 副産物: 冪等
    • DELETE/INSERTではすでにレコードがあってもなくても操作できる
    • cf. UPDATE方式は、新規登録時は代わりにINSERTする必要がある

残高更新は同時でなくてもよい

  • コトの記録と残高(状態)の更新は厳密なトランザクションとして処理されるべきではない
    • 厳密に同時である必要なし
      • 【補】結果整合性の話をしている??
      • 数ミリ秒遅れても問題ないこと多し(要件依存)
    • 二次的な「残高」の更新に失敗したからといって、本質である「コト」の記録も取り消すのはおかしい
  • 同時でなくていいので、非同期メッセージングの可能性が出てくる
    • システムの設計をシンプルにできる

残高更新は1ヵ所でなくてもよい/派生的な情報を転記して作成する

  • 残高更新を複数のサーバで別々に行っても良い
    • PUB/SUB
  • イベントソーシング
    • コトの記録を情報源として、派生するさまざまな情報を目的別に記録する
    • メリデメ
      • メリ
        • 参照系がシンプルになる
        • 目的ごとに柔軟に修正や拡張ができる
      • デメ
        • 厳密な即時性、派生データ間の整合性の保証には相応の仕組みが必要

コトの記録から状態を動的に導出する

  • 副産物的なうれしいこと
    • 将来のコトを擬似的に発生させることで、未来状態のシミュレーションにも使える
    • 過去のある時点での状況を擬似的に作成し、テスト環境として利用できる

オブジェクトの設計とテーブルの設計

オブジェクトとテーブルは似ている

  • 似てるだけ

違うものとして明示的にマッピングする

特性 オブジェクト テーブル
目的 データとロジックの整理 データの整理
関心事 導出や加工、判断 導出や加工の元となるデータ
アプローチ ボトムアップ(部品を組み立てる) トップダウン(整合性のため)
設計変更のリズム 頻繁 ゆるやか

オブジェクトはオブジェクトらしく、テーブルはテーブルらしく/業務ロジックはオブジェクトで、事実の記録はテーブルで

  • ドメインオブジェクトの設計にフレームワークの都合を持ち込まない
  • ORM
    • オブジェクトとテーブルとは本質的に異なる
    • このことを踏まえて設計されたツールなら良い
      • 例: SQL Mapper
    • ドメインオブジェクトのコードにテーブルを意識させるものはダメ
    • 自動化するな
      • ドメインオブジェクトの設計のためにもテーブル設計のためにも良くない