というわけで今日のテーマはドメインである。
集合を定義する
リレーショナルモデルにおけるデータ型とは何か。リレーショナルモデルを実践するにはまずその点から理解する必要がある。リレーショナルモデルでは、データ型はドメインと呼ばれる。ドメインとは、その属性(≒カラム)に入るべき値はどういったものかを集合として定義したものだ。言い換えると、属性値とはある集合の要素の一つであると言える。従って、ドメインを設計する際には、SQLで言うところのデータ型、つまりINTやCHARといったものだけでなく、その属性の値はどうあるべきかという点まで考慮しなければならない。
背後にどのような集合があるかというのを意識することで、
- 一つのカラムとして定義して良いのか、あるいは分けるべきか
- SQLのデータ型は何にするべきか
- 制約をつけるべきか
データの本質を見極める
先日、Twitterで次のようなコメントを頂いた。@nippondanji ところがタプルの値は一意に決まるんですよ(渡辺さんも「一意キー」と書いてますよね)。では「値が変化するってのはどういうこと?」と思われるかもしれませんが、たとえば「学籍番号が6桁から9桁に増えた」みたいに付番体系が一気に変わることを指すことが殆どです。
— SODA Noriyuki (@n_soda) December 3, 2013
これはデータ型を考える上で非常に格好のサンプルであるため使わせて頂く。学籍番号を「6桁の数値」として定義したとする。それを表現するためにCHAR(6)を使ったとしよう。「学籍番号が6桁から9桁に増えた」というのは、例えば123456という番号が000123456というようなものへと変更されることを指しているのだと仮定する。なるほど、これは確かに文字列として比較すると先頭の0があるため一致しない。だがよく考えて欲しい。いずれの場合も数値としての値は同じではないだろうか。
桁数とは、本質的には表示の問題である。
学籍番号を何桁まで書いたところで、その数値の持つ意味は変わらない。学籍番号は数値なのだからデータベース上では数値として表現するべきであり、表示の問題はアプリケーションに任せるべきなのだ。さらに言うと、学籍番号にとって適切なドメインとは有限個の自然数(0を含まない非負の整数)の集合であって文字列ではないということだ。MVCでモデルとビューを分けるのと同じように、リレーショナルモデルではドメインからは表示に関する部分は切り離し、本質的なデータだけを格納するべきなのである。そうしなければデータベース設計がおかしなことになってしまうだろう。前述のツイートはサロゲートキーの話のエントリを書くきっかけになった一連のやりとりの一部であるが、ドメイン設計の誤りをサロゲートキーで補うのは、(当然そういった対応が必要なケースも多々あるが)本来あるべき対応ではないと言える。
ちなみに、「ドメインに含まれる値を在籍している生徒が持つ学籍番号だけに絞るべきではないか」という意見があるかも知れない。その意見は確かに正しいのだが、それはドメインではなくリレーションの役割である。リレーションとは真となる命題の集合であることを思い出して欲しい。リレーションの述語が、例えば「Xという学籍番号を持ち、Y年入学、Zという名前の(中略)生徒が存在する」というようなものと仮定すれば、リレーションに含まれる学籍番号だけが存在するということをリレーションが表現することができるのである。(閉世界仮説)
IDとは
モノや人にラベルをつけて、それを一意に特定する。アイデア自体は至って分かりやすいものだが、これが極めて難しい。データベース設計で重複する値の代名詞のように語られる人名ですら、本来は人を特定するためのIDとしての機能を持つものであった。ごく小さな大昔の集落のような単位であれば、もしかすると重複がないように人名をつけることができたのかも知れない。だが、人口が飛躍的に増えてしまった現代では、もはや人名がIDとして機能しないというのは、皆さんご存知の通りである。
ところで、その人を区別したいのなら生体情報ならどうだろうか。例えばDNAならば人それぞれ違ってるからIDになると考える人も居るかも知れない。だが、DNAにも様々な問題がある。例えば一卵性双生児であればDNAは重複するし、DNAは常に細胞内で破壊と再生が繰り返されているので、生涯を通じて一意なDNAが採取できるとは限らないし、そもそも同じ人物でもガン化しないレベルで細胞ごとに違っているのが普通である。網膜パターンや指紋であれば重複しないと仮定することは可能だが、事故で目や指を失ってしまったりする可能性を考えると、それを人のIDとして使うことは難しい。総合的に考えると、生体情報をIDとして使用するのは無理があるだろう。
同じように、IDとして使用することができないデータが世の中には溢れているが、当然そのようなデータはキーとして使用するのは不適切である。そのような場合、あるモノや人を特定したいならば、IDとして別のドメインが必要であるということが分かる。そういった場合に、シーケンスやAUTO_INCREMENTを使うのは理論的には間違いではない。「シーケンスやAUTO_INCREMENTを用いたカラム=サロゲートキー」という誤解が非常に多いのだが、サロゲートキーとはあくまでもナチュラルキーがあるにも関わらず扱いを容易にするために導入するキーのことである。他にIDとして機能する属性がないのであれば、その機能を提供するドメインは、必然的に新たに設定する必要があるため、それらをサロゲートキーと呼ぶのは間違っている。(何かの代理ではないからだ。)
人名がそうであるように、IDとは本質的には人がつけたラベルである。ナチュラル(自然)キーという言葉は、「個体を特定できるような情報が自然界に隠れていて、それを見つけ出す作業がIDをつける行為」という風な考えを想起させてしまうが、それは誤った認識である。人の手によって、如何に重複しないラベルを設計するかが良いIDを選択する上でのポイントであると言える。
Railsの規約について
IDの話をしたついでに、Railsの自動採番されたIDを持つという規約についてもひとこと言っておく。クソであると。
前述のように自動採番されたIDが必要なケースは存在する。そのような場合、そのカラムをキーとして使うことは設計上間違いではない。一方、すでにナチュラルキーが存在する場合には、規約よりもリレーショナルモデルを優先し、ナチュラルキーを使うという選択は常に考慮に入れるべきである。Railsでは規約から外れてナチュラルキーを利用することは可能なので、その仕組みを利用すれば良い。Railsの規約はこういったデータモデルの要件を無視して一律に同じにしてしまっているからクソなのだ。
ちなみに、どうしてものRailsの規約に違反したくないということであれば、ナチュラルキーを主キーではなくセカンダリなユニークキーとして定義するという選択もアリだ。リレーショナルモデルでは主キーとそれ以外のキーの区別はない。それを逆手に取れば、ナチュラルキーをユニークキーとして定義するというのは、リレーショナルモデル上は特に問題のある選択肢ではないと言える。ただ自動採番された無駄なIDという名前のカラムが存在するだけである。無駄なカラムは存在するが、それを無視してあたかも存在し無いかのように扱ってしまえば、それは単にフレームワークの実装上の問題となり、アプリケーションのコードにはほとんど悪影響はない。フレームワークがそういった下らない規約を要求するのであれば、致命的な問題でなければとりあえず従っておくというのは悪い選択ではないと思う。(無駄があるので性能的には不利になるだろう。)ただし、そのような扱いをすると決めたら、外部キーでもIDカラムを参照するべきではないし、アプリケーション上でそのカラムは参照してはならない。
如何なるフレームワークであっても、リレーショナルデータベースを使う以上はリレーショナルモデルをしっかりと理解した上で使うべきである。
同じ意味のカラムには同じ名前をつける
ドメインを設計する上でもうひとつ注意すべき点は、同じ意味のカラム、つまり同じドメインを持つカラムには同じ名前を用いるという点である。同じ名前をつけておくことで、データベース設計の見通しが良くなり、直交性などの問題が見つけやすくなるからだ。ところで、SQL的に同じデータ型を持つカラムであっても、異なるドメインのものを直接比較することには意味はない。例えば、同じ自然数であっても、学籍番号と期末テストの数学の点数を比較することには意味はない。重要なのはドメインである。テストの点数同士の比較であれば、同じドメインに属する値であるから、比較には意味を持つ。
逆説的に考えると、比較するに値するかどうかということが、同じドメインを持つ属性を見つける手がかりになる。もし同じドメインを持つ属性が見つかったら、それらを同じ名前にし、ドメインを一意に特定できるようにしておくべきである。
当然ながら、同じ意味を持つ属性であるということは、SQLのデータ型だけでなく値、つまりドメインに含まれる個々の要素も共通のものでなければならない。二つの属性が共通の値を持つが、互いに素な要素を持つのであれば、完全にドメインは一致しないと言えるので、そのような場合には異なる意味の属性であると考えることができる。似ていても異なるドメインを持つ属性は、異なる名称にしておくべきである。
意味を持つID
非常によくある問題として、IDの一部に意味を持たせているケースがある。例えば製品や部品、商品などの品番などは、その先頭部分などからどのような種類のものであるかということを推測できるようになっていることがある。他にも、基礎年金番号の先頭部分は事務所を表すことになっていたり、郵便番号を見れば都道府県がどこなのかが分かったり、グローバルIPアドレスを見ればそれがどの国のものかが分かったりもする。そういった「IDから何かを推測することができる」という性質は便利なものなので、世の中はそういったもので溢れている。そういった便利な使い方を否定することはないが、データベース設計では注意が必要となる。結論を先に言うと、データベース設計ではそのような「値の一部に意味を持たせる」という設計はバッドデザインだ。
理由は様々なものが挙げられるが、根本的に一部に意味があるような値は、アトミックではない。アトミックでない値を含んだリレーションは1NF(第1正規形)には成り得ない。データベース設計理論が端から破綻してしまうという問題がある。
属性値の一部に依存した処理が問題だというのなら、元の値はそのままにして、その一部だけを別の属性として持つという方法なら良いではないかと考えるかも知れない。例えば、種別(文字列3桁)番号(数字4桁)(例:AAA0001)というようなフォーマットの商品コード属性があった場合、その商品のカテゴリを表す種別の部分だけを別の属性として持つという考えである。アトミックでない消費コードを単一の属性として持つだけの設計よりは(属性を分解する必要がなくなるので)クエリを書く上ではマシになるが、それでもその属性そのものの持つ問題は解決していない。そう、先ほどと同じ議論になるが、フォーマットは表示の問題だからだ。
従って、部分文字列が意味を持つようなIDは、「値がアトミックでない」「表示の問題がデータに紛れ込んでいる」という2つの問題があり、合わせ技で1本。リレーショナルデータベース上では本来使うべきではないデータ表現であると言える。
先ほどの学籍番号の例と同じであるが、こういったデータもデータベース上では表示とデータは分けるという方針に基づいて設計したほうが良い。種別(文字列3桁)番号(数字4桁)というようなフォーマットの商品コード属性は、種別(文字列)と番号(非負の整数)という2つの属性として定義するのが自然である。当然、その値をリレーションのキーとして使うのであれば複合キーとなる。そのデータから実際に表示されるIDへの変換、あるいは表示されたIDからデータへの変換は、アプリケーション側で行うべき処理である。変換処理の書き換えで適用できる範囲であれば、データベースの定義を変更せずに要件の変更に対応することができるだろう。
ちなみに、ここで今説明したような設計はポピュラーではない。何故なのかは私には分からない。もしかすると複合キーへの忌避感が背景にあったりするのかも知れない。(複合キーはリレーショナルモデルでは必須の概念なので、抵抗を感じることなく使うべきである。)もしくは、IDをどう設計すべきかということは、これまであまり議論されず、見過ごされて来てしまった問題であるのかも知れない。それとも、表示とデータは同じでなければならないという固定観念があるからかも知れない。
ドメイン設計の問題は波及する
ドメインは正しく設計すべきである。特にIDとなるドメインの問題を放置していると、その問題は他の箇所にどんどん波及してしまう。「キーが変更に弱いからサロゲートキーで何とかしよう」というアイデアも、単にドメインの設計という問題から目を背けているだけに過ぎない。その場しのぎの対策は、必ず技術的負債となって重くのしかかることになる。誤った理論に基づいて設計されたデータベースに対して書かれたクエリは負債となる。負債はひとつのテーブルだけにとどまらず、同じ意味の属性を格納するテーブル全てに波及する。そうしてデータベース設計が負債だらけになる。いったん大きな負債を抱えてしまうと、データベースは変更に弱くなり、クエリは複雑になり、デバッグもより困難になってしまう。負債には利子がつく。身動きが取れなくなってしまう前に負債は返すべきである。
最悪の場合、特にIDとなる属性に対するドメイン設計の問題は、単一のアプリケーションだけにとどまらず、そのアプリケーションが生成したIDを使用するすべてのアプリケーションに波及しかねない。そうなると、「品番の桁数を変更するだけで全社的にアプリケーションの変更が必要になる」というような悲しい事態に陥ることになる。
あくまでも取るべき対策は、ドメイン設計をきっちり行うということである。ドメイン設計に問題があれば見直しをしなければいけない。だが、大規模な企業の業務システムなどでは、そのような作業はSIerには出来ないことかも知れない。IDの管理ができるのは、業務全体を見渡すことのできるユーザー企業自身だけだからだ。従って、ユーザー企業が責任をもって自分たちの業務で必要なドメインを管理する必要があるのである。
また、IDをどう定義するかはもちろん重要だが、適切な運用が行われるようにすることは更に重要となる。例えば、書籍のISBNなどは本来重複してはならないものだが、運用が厳格ではなかったために多数の重複が含まれてしまっている、運用に失敗した例であると言えよう。そうならないよう設計時に運用まで考慮しておくべきだろう。
まとめ
ドメインの設計は極めて慎重に行うべきである。正規化などは法則に従って実行するだけなので、ある意味ドメインの設計に比べると簡単な作業であると言える。長々と語ったが、今回のエントリで主張したことをまとめると、次のものに集約される。
- データベース設計時にはドメインを意識する
- 表示とデータは分ける
- 同じ意味のカラムには同じ名前をつける
- IDとなるドメインの設計時には運用まで視野にいれておく
- 一つのカラムとして定義して良いのか、あるいは分けるべきか
- SQLのデータ型は何にするべきか
- 制約をつけるべきか
0 件のコメント:
コメントを投稿