データベース設計の中でも、テーブル定義は奥が深い。
よほど素質がある人じゃなければ、習熟には経験が必要だ。
若い頃、僕はテーブル定義が苦手だった。
設計をした後、見返してみてもいまいちそれが正解だと言い切れない、そういう感覚があった。
そうして定義されたテーブルは、時間を置いてみてみても、すっと頭に入ってこない。
定義書を見ないとテーブル定義が思い出せない。
そういうテーブル定義を渡すと、プログラマも混乱しがちだった。
これは、そのテーブル定義が自然じゃないから、だと僕は考えている。
僕に指導されている人はよく聞いていると思うけど、テーブル定義の肝は、いかに事実を正しくデッサンするか、だ。
テーブルはプログラムの変数の置き場じゃない(※)。
たとえばそれが業務システムの中核をなすデータベースなら、業務の動きとデータを、事実をじっと見つめて、自然な形で書き出すことが大事だ。
経験がないと、どうもこれができない。
画面のレイアウトや帳票フォーマット、機能ギミックを考えながらテーブル定義をしてしまいがちなのだ。
画面なんか、どうでもいい。
そんなもん後で考えろ。
すべてのテーブルが、事実と照らして自然かどうか。
これができていないと、後からの仕様変更や機能追加に対して目も当てられない弱さを露呈する。
抽象的な話になってしまったので、少し具体的に。
たとえば”お客様”から”注文”を受ける業務があるとする。
テーブル定義をするとこんな感じのテーブル(エンティティ)を定義するはずだ。
・お客様
・注文(注文日時、お客様ID、お届け先、合計金額、消費税 etc...)
・注文詳細(注文商品、数量、金額)
ここまでは単純な正規化だけでできるので、迷う人は少ない。
だが、あとからクライアントが次のような要望を出してきたらどうだろう。
「注文するお客様の中には卸業者さんがいて、1回の注文で、同じ注文内容で複数のお届け先を簡単に指定できるようにしてほしい」
・・・この話を受けて、まず画面を考えたりしてないだろうか?注文内容確認画面で、配送先編集画面に”配送先を追加する”みたいなボタンを付けて・・・なんて。
そんなのはどうとでもなるので、まずはテーブル定義をどうすべきかを真剣に考えよう。
過去に、このようにテーブル定義をしているケースを実際に見た。
・お客様
・注文
・注文詳細
・同時お届け先
同時お届け先とは、画面で指摘できる第2第3のお届け先だ。
・・・不自然過ぎる。
現在のプログラムをあまり変えないで、あらたに付加したテーブルだけで気軽に帳尻を合わせようとしている設計者の短絡さが伺える。
このテーブル定義の何が悪いのか、とりあえず2つ上げてみる。
- 1. いくら商品構成内容が同じでも、複数のお届け先があるということは、その数だけの複数の別々の”注文”なのだ。それをデッサンできていない。
- 2. 一つ目のお届け先と、2つ目以降のお届け先が別のエンティティになっている。注文者の思惑として指定順になにか意図があったとしても、同一エンティティとすべきなことは疑いようもない。
2もかなーり気持ち悪いが、なにより1が重罪だ。
こんな付け焼き刃テーブルを定義したせいで、”注文件数=注文エンティティのレコード数”という、ごくシンプルで自然な正義が崩壊してしまった。売上を計上するにも、注文エンティティではなく同時お届け先エンティティに注目しないといけなくなった。
非常に不自然だ。
画面に引っ張られている、もしくはテーブル定義を簡単に済ませようとしている。
この場合正しいテーブル定義はこうだ。
・お客様
・注文
・注文詳細
そう。何も変わっていない。
卸業者なり誰かなりが複数のお届け先を画面で指定できたとしても、データベースは厳然として複数の注文として保存する。
「いやいや、卸業者から見たら1回の注文で5件に配送した、そういうふうに見せる必要があるんですよ!」
もしそう言ってきたとしても、おちつけ。それ、画面の話だろ。
複数の注文をグルーピングすればいいだけだ。
そのグルーピングがエンティティとして重要なのであれば、
・一括配送依頼
などとして、そのidなり依頼番号なりを関連する注文エンティティにもたせてやれ。
画面上の表現なんかのために、エンティティの自然な正義を壊してはいけないんだ。
仕様変更をものともしない、柔軟なシステム設計。
その足腰は、事実を正しくデッサンしたテーブル定義にのみ宿る。
※パフォーマンスチューニングのためのデータベース拡張は、応用の話として別と考えてます。今回はあくまで、必ず抑えておくべき基本の話ってことで。