2017.11.29

クラス図とデータモデルはどうちがう?

 しばらく前に、初対面の先輩技術者とDB設計について話していたときのことだ。彼が書いた「データモデル」を見せてもらったのだが、そこには主キーが示されていなかった。不思議に思って尋ねると、「主キーを何にするかは実装上の問題なので、示してありません」という答だった。

 これには驚いた。「鉄筋の本数は施工上の問題なので、設計図には示してありません」と言われたようなものだ。主キーはシステムが扱うデータの論理構造を表すために不可欠なもので、それが示されていないデータモデルはセンチメンタルな「ポエム」でしかない。

 そういうものにもとづいて高層ビルを建設すれば施工中に倒壊して、設計図がポエムでしかなかったことが明らかになる。ところがシステム開発の場合、現場が苦労に苦労を重ねるとまがりなりにも出来上がってしまうので始末が悪い。設計者は自分のやり方を是正することなく、いつまでも彼の文学作品をデータモデルとして世の中に供給し続けるだろう。

 気を取り直してみて、ふと思うところがあったので訊いてみた。「もしかしてXXさんのやり方はオブジェクト指向に影響を受けていませんか?」すると案に相違なくそうであるとの答だった。そのとき私はその図がデータモデルではなく「データモデル的な別のナニカ」であることを理解した。同時に、データモデルとクラス図とが開発現場で混同されることがあるという危うい現実を知った。

 そんなこともあって先々週に、IT勉強宴会でデータモデルとクラス図の違いについて議論したのだが、そのときに得た知見を織り交ぜつつ、両者が似て非なるものであることをあらためて説明したい。両者は生まれも育ちも異なる図法で、データモデルが発展してクラス図が生まれたのでもないし、データモデルの足りないところを補完するためにクラス図が生み出されたのでもない。その違いを「適用範囲」、「関数従属性の扱い」、「設計根拠」、「利用タイミング」といった側面から説明しよう。

■適用範囲の違い

 テーブルはシステムが扱うデータを「永続化」するための入れ物だが、クラスの中には「永続化されないクラス」もある。永続化されるクラスがテーブルと一致するわけではないのだが、クラスの適用範囲がデータモデルよりも広いことは明らかだ。したがって、データモデルをクラス図として作り変えることは一般に可能ではあるが、永続化されないクラスを含むクラス図をデータモデルとして書き直すことはできない。

 では、「永続化クラスだけを含むクラス図」をデータモデルとして書き直すことは可能なのだろうか。それなりに可能である。オブジェクト指向分析・設計(OOA/OOD)やその現代版であるドメイン駆動設計(DDD)でDBを利用する場合、テーブルは「永続化クラス」に対応するものとして作成される。上述した「データモデル的な別のナニカ」はこのような文脈で出現する。それにもとづいて「主キー(idのような単独主キー)」を持つテーブル的なナニカが実装され、そこにオブジェクトが記録される。

 ところがこのやり方では、複雑なデータ要件の仕様化に失敗する。次項で述べるように「データモデル的な別のナニカ」の上で「関数従属性」が考慮されることが稀だからだ。

■関数従属性の扱いの違い

 DB設計者がクラスモデリングをしているのかデータモデリングをしているのかを見分ける簡単な方法がある。「複合主キー」を避けようとしているとすれば、その開発者はDBをクラス構造として捉えようとしていると判断できる。複合主キーを要請するクラスは基本的に存在しないからだ。永続化クラス毎にテーブルが用意され、それぞれに単独主キーとして「ID(オブジェクトID)」が付与される。

 いっぽうのデータモデリングにおいて複合主キーが当たり前に組み込まれるのはなぜか。データ要件中にそのような「関数従属性」が当たり前に存在するからだ。単純な例だが、図1での{d、f}→gや{d、f}→hのようなデータ要件はふつうに見られる(a→bの表記は「項目aに項目bが関数従属する」すなわち「項目aの値を決めれば項目bの値が定まる」を意味している)。私の経験では、全テーブル中の約半数が複合主キーにもとづく関数従属性を伴う。

図1.複合主キーを伴うデータ要件と関数従属性

 [A]{}、b、c ... a→b a→c
 +
 │
 └…[D]{}、a、e ... d→a d→e
   +
   │
   └∈[DF]{d、f}、g、h ... {d、f}→g {d、f}→h
   ┌∈
   │
   +
   [F]{}、i、j ... f→i f→j

 単独主キーであっても複合主キーであっても、主キーであるからには、その値が全レコード中で重複してはいけない(ユニーク制約)。なぜならこれに違反すれば関数従属性が成立しなくなるからだ。また、主キーの値はレコードが追加されて削除されるまで更新されてはいけない(更新制約)。なぜなら主キーの値が変化すれば、テーブル関連を維持できなくなるからだ。したがって、全レコード中でユニークであるとしてもその値が変化し得る「自然キー」は主キー(一次キー,Primary Key,PK)ではなく、更新制約が付与されない「二次キー(Secondary Key,SK)」とされなければいけない。

 図1をクラス図に置き換えれば図2のようになる。決定的な問題は、クラスモデリングでは複合主キー要件が事実上考慮されないので、{d、f}→gや{d、f}→hの関数従属性が見落とされる点である。その結果、DFテーブル上でD-idとF-idの値の組み合わせが重複するようなデータ状況を許し、かつユーザによってそれらが更新可能であるようなUIが生み出される。遅かれ早かれユーザは矛盾するデータに悩まされることになる。

図2.{D-id,F-id}の重複と更新を許すクラス図

 [A]{id}、b、c
 1│
  │*
  └─[D]{id}、A-id、e
   1│
    │*
    └─[DF]{id}、D-id、F-id、g、h
    ┌─
    │*
   1│
    [F]{id}、i、j

 この指摘に対して、「OCL(Object Constraint Language,オブジェクト制約言語)を使えば、複合主キーを含む複雑な制約もクラス図上で表現できる」と説明されたりするが、有効な反論とは言い難い。なにしろOCLを活用している開発現場は稀であるだけでなく、複合主キーが嫌悪されることはあっても、積極的に利用されるケースが稀だからだ。「記法として示せるかどうか」ではなく「実務としてどうか」が問題である。

 なお、図2でも示されているように、クラス図上には1対*(複数を表す)等の「多重度」が示されるが、これもなかなかクワセ者だ。たとえば図3のようなクラス図を目にすることがある。

図3.多重度が矛盾しているクラス図

 ┌─[A]{id}、b、c
 │1
 │
 │0..1
 └─[D]{id}、A-id、e

 D上にAの主キーが外部キーとして載っているので、Aの1レコードにはDのレコードが複数件対応するように見える。ところが「0または1件のみ対応する」と示してある。多重度が矛盾しているのではないかと設計者に質すと「大丈夫です。0または1件のみ対応するようにアプリが出来ていますから」という答が返ってきたりする。

 そのような言い訳はクラス図ならば許されるのかもしれない(だとしたら困った話だ)が、データモデルでは許されない。データモデル上での多重度は「アプリの仕様が決まっていなくても、そのようになることが構造的に保障されているもの」として示されなければいけない。「データモデル上で示された多重度を保証するように、アプリの仕様を考える」わけではないし、「そのようにアプリの仕様が出来ているゆえに、非論理的なデータモデルでも許される」わけでもない。

■設計根拠の違い

 なお実際には、図1のデータ要件に対応してきれいに図2のようなクラス構成が切り出されるわけではない。データモデリングでは「関数従属性」や「ドメイン(定義域)制約」といった数学的特性がテーブルを切り出すための手がかりとなるが、クラスモデリングでは「プログラミングにおいてそれがクラスとして必要と判断された」ゆえにクラスが切り出される。結果的に、切り出されるエンティティ構成は大きく違ってくる。

 クラスモデリングの結果に対して「これがクラスとして切り出された理由は何ですか」と訊かれたら、「それで効果的にプログラミングできると考えたからです」と答えるしかない。問うた方も「ああそうですか」と受け入れるしかない。いっぽう、「これがテーブルとして切り出された理由は何ですか」と訊かれたら、システムが扱う現実をどのような論理構造として解釈したかを設計根拠として持ち出せる。この違いは大きい。

 なぜかというと、関数従属性やドメイン制約を付与された現実には、科学の基本原則たる「反証可能性」があるからだ。システム要件から効果的な概念を識別するには経験や直感が要るが、それらがどのような論理構造として配置されたかについては、その正しさを統辞論レベルで評価できる。いっぽうクラス図は意味的にしか評価できない。

 たとえば、図2の「DF」は「D,F上のインスタンスを参照し、g,hをプロパティとするクラス」であって、その妥当性を云々するには、D,F,g,hの具体的な意味に頼るしかない。いっぽう図1の「DF」は「テーブルDとテーブルFの主キーの組み合わせを主キーとして、これに関数従属するg,hを置いたもの」である。ここから「gとhは{d,f}に関数従属する」という「現実として反証可能な言明」を取り出せる。「各要素の意味」だけでなく「要素間の論理関係」を設計妥当性の検証に使えるということだ。

 クラス構成の妥当性を評価するには、その「意味」のみに頼らざるを得ない。オブジェクトモデルの「パターン」が求められるのもそのためだ。そういった手がかりがないと、システム要件を効果的なクラス構造に落とし込むのはずっと難しいだろう。そういったパターンは、具体的なモデリングの文脈に沿ったものであって、図2や図3のような記号表現では使いものにならない。じっさい私は図1のように、データモデルのテーブル名やフィールド名を記号化して説明することが多いのだが、それが可能なのも「要素間の論理関係」だけで議論ができるという特性のおかげである。

 また、クラス図上に各クラスの「メソッド」を付記することがあるが、これにもクラスの意味を補強するという役目がある。どんなメソッドを持つかは、クラスの「意味」を理解して設計妥当性を評価するための重要な手がかりだ。いっぽう、データモデル上のテーブルにメソッドを付記するとしたら、常にCRUD(追加・更新・削除・読取)の4種類だけなので、それらを示すことにほとんど意味がない。

 こういった主張に対して、「そもそもクラス構成は、アジャイルに実装され動作確認されることで、要件を適切に扱えるかどうかが検証される。それが設計根拠だ」といった意見があるかもしれない。直ちに動作確認できる点についてはデータモデルも同様で、そのための手段が今ではいくらでもある。大事なのは、実装する前に、書かれた図面の正しさを理屈を援用した形で検証できることだ。理屈で検証してさらに実装して検証を重ねることと、正しいかどうかわからないから実装するとでは大違いだ。つまり、オブジェクト指向開発では「アジャイルが強制される」のである。

■利用タイミングの違い

 データモデルがプロジェクトにおいて利用されるのは、実装以前の段階(要件定義あたり)からだ。いっぽうクラス図が実装以前に提出されても、上述したようにその内容が妥当かどうかよくわからない。いきおい、「クラス構成をきれいな図にまとめるヒマがあるなら、とっととプログラミングしようよ」となる。そういうわけなので、整然としたクラス図はたいてい、プログラミングの途中か完了後にまとめられる。

 このように、利用タイミングが実務上では異なるのだが、一見すると両者は似ているので、クラス図がデータモデルとして通用すると勘違いされる。その結果、冒頭で書いたように、主キーのない「データモデル的な別のナニカ」が要件定義成果物として納品されるようなことが起こる。納品した本人に言わせれば、この論理的なエンティティ構成にもとづいて、主キーに「id」を置いてオブジェクトの永続先としてもよし、主キーを丁寧に設計してDOA風に実装してもよし、そこらへんは実装担当者の裁量にまかせる――となるのだろう。

 しかしこれはきわめて危険かつ無責任な設計方針と言わざるを得ない。主キーで関数従属性や多重度を適切に規定するには、DB設計まわりのスキルや経験が要る。そういう仕事を実装担当者に丸投げしてはいけない。少なくとも主キーは明示すべきだ。プロジェクトがグダグダになったとき、主キーが示されていればデータモデルを書いた技術者の責任として跡付けできるが、そうでなければ実装担当者の責任にされてしまうからだ。

■まとめ

 あなたが誰かの設計成果物として「データモデル」を受け取ったなら、主キーをよく見てほしい。単独主キーばかりだったり、反対にやたらと多くの項目を含む複合主キーがあったり、そもそも主キーが示されていないとしたら、納得いくまで設計意図を説明してもらうべきだ。納得できないなら、そんな危うい設計を許すプロジェクトとは関わらないほうがいい。

 なぜか。主キーにこそシステム設計者の馬脚があらわれるからだ。主キーがいい加減なデータモデルは、中学生が書く恋愛ポエムのようなものでしかない。まあポエムであっても、テーブル数が20本以下の案件ならば、プログラマががんばればなんとかなるだろう。しかし30本以上であれば、プロジェクトは要らぬ苦労を引き受ける羽目になる。50本以上ならばデスマーチ確実だ。

 では、数十本以上のテーブルを含む案件があって、これを安全にオブジェクト指向開発するにはどうすべきか。まずは複合主キーを含んだ形でテーブル構造を丁寧に正規化しよう。上述したようにそれは簡単な作業ではないが、これがしっかりなされない限りプロジェクトは失敗する。正規化に続いて、複合主キーをサロゲートキーに置き換えるための「正規化崩し」をする。その際には、上述した「ユニーク制約」や「更新制約」についてアプリで丁寧に考慮するように明文化しておこう。そのうえで、ORマッパーを使ってDBを処理すればよい。

 言うまでもなく、わざわざ「正規化崩し」せずに、複合主キーを受け入れるフレームワークやクラスライブラリを使えるのであれば、そのほうがずっといい。永続化オブジェクトとRDB間のインピーダンス問題に悩まされることなく、伸び伸びと開発できるからだ。さらにいいのは、オブジェクト指向が隠ぺいされた、業務システム専用の開発基盤を使うことだ。データモデルに対応するクラス構成をわざわざ考える必要がないからだ。けっきょくのところ、RDBを扱う一般的な業務システムの開発案件は、オブジェクト指向にとってどうにも「役不足」なのである。

| | コメント (2) | トラックバック (0)

«危うい「プロセス指向」が廃れない理由