関連の設計と実装



オーダーと明細のモデル。

明細は、最低一行ないといけない、というのがビジネスルール。(構造ルール)




Java で実装すると、 Order クラスが、 List コレクションへの参照を持つ。
明細が最低一行必須、というビジネスルールは、メソッドで実装する。




同じモデルをテーブルで実装する。
参照が逆転しちゃいますね。

Java だと、Order クラスが List を知っている。 List は、 Order クラスを知らない。

Order テーブルは、Item テーブルへの情報を持たないけど、Item テーブルは、Order Number を参照している。(外部キー参照)

参照の逆転

マーチンファウラーのPoEAA にでてくる外部キーマッピングですね。
もっとも、PoEAA では、このパターン(コレクションへの単方向の参照)は、依存マッピングを推奨している。

このテーブルモデルは、Item の主キーである object_oid の扱い方の問題。

依存マッピングでは、
・object_oid での検索はしない。( Item を単独で検索はしない )
・テーブルの主キーの object_oid は、 Java のオブジェクトでは持たない ( 一意フィールドを持たない)
という実装をする。

同じモデルを、Java で実装するのと、テーブルで実装するのでは、参照関係が逆転。この逆転構造を、マッピングする課題を検討しているのが、PoEAA 12章 O-R 構造パターン、というわけだ。

朽ちていくD51

飛鳥山公園に行ったら、D51が展示されていた。
子供のころ、D51の車両置き場が遊び場だった私には、たまらなく懐かしい。
853号機。経歴調べると、私の子供のころは、ずっと長岡機関区だったらしい。私が見ていた一台かもしれない。

運転席に入れるし、動輪や連結棒を触ったり、下部の構造を下からのぞいたり。子供の時にやりたかったけどできないことをいろいろやってみちゃった。子供の時にも大きいと思っていたけど、いまでも、かなりでかいのにちょっとびっくり。

塗装も綺麗だし、よく整備されている。

でも、よくよく見ると、あちこち腐食している。
稼動部分も、ほとんど溶接止めされている。
細いパイプ類も破損箇所が多い。

しみじみ見ていると、延命治療はしているが、死にゆく運命を変えようがない姿にせつなさがこみあげてきてしまった。

たいへんなことなんだろうけど、やはり動く状態で保存してあげたい。
今でも、ほんとうは走りたいだろうに。走るためだけに生まれてきたんだから。

ドメインオブジェクトの粒度 : XML とのマッピング

小さなオブジェクト

このモデルを XML ドキュメントにマッピングすると、1ファイル。

<employee id="4822" >
ここにオブジェクトをツリー上に並べていく
</employee>

基本的に、システム間のデータ送信に使うので、一回の送信単位としては、こんな感じでしょう。

インタフェース用のオブジェクト

XML ドキュメントは、基本は、画面の単位と同じと思っている。

ユーザとのインタフェースが、画面。
システム間のインタフェースが、XML。

こういうインタフェースの粒度は、大きな単位にまとめるのが普通ですね。

画面やXMLに限らず、RMI やデータアクセスでも、小さなオブジェクトを、DTO ( Data Transfer Object ) という入れ物にまとめて、大きな単位で処理する。

JAR や WAR も、考え方としては同じですね。
小さなオブジェクトの集まりを移動して配置するために、扱いやすいように、大きな単位にまとめる

Java < テーブル < XML

結局、同じモデルを実装する粒度は、

Java : とても小さく分割 (7 つのクラス)
テーブル : まとめる (2つのテーブル)
XML : 1つにまとめる

という違いがでてくる。

UML のクラス図との比較で言えば、私の場合は、UML のクラスアイコン数より、Java のクラスの方が多い。

例えば、上の図だと、Money オブジェクトを、アイコンとして記述しているけど、クラス図では、普通は、ここまで書かない。給与オブジェクトの中で表現しておしまい。

String が登場するたびに、クラス図に String をクラスアイコンとして書いていたのでは、仕事になりませんからね。 作業が面倒というより、図にノイズが増えて、モデルの本質がどこかにいっちゃうから。


ドメインオブジェクトの粒度 : テーブルとのマッピング

ひとつ前の記事で書いた、ドメインモデルをテーブルに実装すると、こんな感じ。

データモデル

クラス図とJava の実装ではクラスの数は、7つだったけど、テーブルは2つにした。

オブジェクト指向設計だと、ドメインのオブジェクトは、とても小さな単位になる。String や Integer などプリミティブなオブジェクトレベルまで、ドメインの用語のオブジェクトを作るのが基本。

それぞれのオブジェクトの役割が明確になり、ロジックの置き場所の見通しが良くなるから。

この小さなオブジェクトたちを、1対1でテーブルにマッピングすると、扱いにくいのは直感的にわかる。
オブジェクトとテーブルのマッピングの考え方は?

Aggregate(集約)

Evans のドメイン駆動設計(DDD:Domain-Driven Design)の基本パターンのひとつに Aggregate(集約)の設計がある。
いくつかのオブジェクトをまとめて、ひとかたまりで考える。

DDD の説明では、Aggregate パターンを、オブジェクトのライフサイクルの文脈で説明している。

・オブジェクトを新規に生成する
・オブジェクトを保存する
・保存したオブジェクトを取り出す
・オブジェクトを消去する

こういう時に、いつも、一つのグループとして扱うべきオブジェクトのかたまりを見つけることが、ドメイン駆動設計の基本テクニック。

DDDの本では、このオブジェクトをひとかたまりに扱う感じを、ブドウの房の写真で象徴している。

どういう単位が Aggregate として、ひとかたまりとして扱うべきかの議論が、ドメインを深く知るための重要な手がかりになる。

オブジェクトのテーブルへのマッピングは、この Aggregate の設計を反映するのが基本。

大きさの制限

ただし、テーブルも、基本は役割別に小さくしておくべき。
なんども繰り返しているけど、関心事の分離は、設計の基本中の基本。

乱暴だけど、テーブルのサイズは、属性を5つくらいまでを目安にしている。

主キー、外部キー、タイムスタンプなどの監査列を除いた、値を持つ列が、5つを超えたら、テーブルの分割の可能性を考える。

もちろん、情報のかたまりの意味を優先するけど、データ列が10を超えたら、完全に、アンチパターン(肥大化したテーブル)と判断している。

テーブル設計の手がかり

実践的には、テーブルの単位は、画面上の「ブロック」を参考にしている。

例えば、画面の氏名の入力フィールドが、

・姓
・名
・セイ
・メイ

4つあると、デザイン的に、4つのフィールドを枠線で囲ったり、背景色を変えたり、「氏名」という見出しをデザインしたりして、ひとかたまりに見せる工夫をする。

このグループが テーブルの単位の第一候補。

業務上、意味があるかたまりだから、そういうデザインにする。そういう論理的なかたまりは、当然、モデリングに反映すべきだし、Aggregate の実装、テーブルの実装でも、意識すべき論理構造。

オブジェクトの粒度:小さなオブジェクトに分ける

役割が明確な小さなオブジェクトに分けるのが、基本中の基本。

小さなオブジェクト

従業員を表現するために、従業員オブジェクトをルートとして、

・個人
・氏名
・電話番号
・生年月日

・期間
・給与

という小さなオブジェクトで構成する。

個人

氏名や電話番号のサブのルートクラス。

氏名

姓、名、セイ、メイを保持
バリデーションや、"姓名(セイメイ)"などのフォーマット出力を担当

電話番号

電話番号のバリデーションとか、フォーマット出力を担当

生年月日

生年月日を保持して、年齢計算も担当

期間

開始日と終了日を保持。
ある期間とある期間が重なっているかとか、期間演算を担当

給与

マネークラスのサブクラス。
将来は、給与計算ロジックを追加する場所。

---

オブジェクト指向の分析設計の発展形である、ドメイン駆動設計のオブジェクトの構成はこんな感じなる。

Evans の Domain-Driven Design のパターンでいえば、

・従業員オブジェクトは、Entity.
・その他のオブジェクトは Value Objects.

・従業員オブジェクトが、Aggregate のルート

この Aggregate の単位で、オブジェクト群を生成するために、 Factory パターンを使う。
Aggregate の単位で、保存と取り出しをするために、Repository パターンを使う。

Java の実装イメージ

class Employee
{
 Person person ;
 DateRange range ;
 Salary salary ;
}

class Person
{
 PersonName name ;
 PhoneNumber phoneNumber ;
 BirthDate birthDate ;
}

class EmployeeFactory
{
 Employee createEmployee() { }
}

class EmployeeRepository
{
 Employee find() { }
 List search() { }
}



---

オブジェクト指向の分析設計で、Java で実装すると、まあ、このくらいの粒度が良い感じ。

かなり小さい。
  • それぞれクラスの役割は単純
  • ドメインロジックをどこに置くかも、単純明快(ロジックが関連する情報を持つクラス)
  • ドメインのロジックに追加や変更をする時、対象のオブジェクトは、すぐわかる
  • 変更の副作用の影響範囲は、特定のオブジェクトに限定される

オブジェクトの粒度 最小単位

ドメインオブジェクトは、どの程度の大きさに設計するか?

小さくする

Java の場合は、できるだけ小さくしている。 単一で明快な責務を持たせるのが原則。

では、

テーブルは?
XML は?
UML のクラス図は?

Java と同じ粒度まで、テーブルやXMLを小さくしてしまうと、扱いにくくてしょうがない。

クラス図も、ひとつひとつのクラスを箱に書き出すと、モデルとして役に立たないくらい、図が複雑になっちゃう。

大きくする(まとめる)

一つのテーブル、ひとつのXMLにたくさんのオブジェクトをぐちゃっと詰め込むのは、明らかにアンチパターン。
設計原則「関心事の分離」に違反している。

かといって、分割しすぎるのも問題。
マーチンファウラの PoEAA の「組込みオブジェクト」パターンの議論で、ここらへんが書いてあったような気がする。
後で調べてみよう。

不一致

どうやら、Java 、テーブル、XMLは、同じ粒度で扱うのは現実的ではなさそう。
不一致が発生するわけだ。
この不一致をどうやって吸収するかも重要な設計問題になる。

ここらへんの実践的なやり方を、少し検討していきたい。

ドメインモデル貧血症

ドメインモデル貧血症をもう一度、考えてみる。

まず、ドメインのオブジェクトとその関連が、きちんと設計されていることが前提。
問題領域の言葉がそのままドメインクラス名になっている。言葉と言葉の関連も、きちんとモデリングされ、Java の参照や、SQL文として実装されている。

少なくとも「情報」とその関連を、きちんと分析して実装していることが、議論の大前提になっている。

ドメインモデルもなければ、ドメイン層もない設計は、そもそも議論の対象外。

貧血症かどうかは、振る舞いの問題。ドメインクラスが、どんなメソッドを実装しているかが、病状の診断基準というわけだ。

代表的なのが、validateion のロジックの置き場所。

Validator クラスに外出しする設計なら、貧血症。
ドメインクラスが、validate() メソッドを実装していれば、ドメインリッチ。

parse() や format() を外出しにしているのも、典型的な貧血症。
Java の Date クラスなんかが典型ですね。DateFormat クラスがわざわざ別にある。

ドメインリッチの良い例が InternetAddress クラス。
インターネットのメールアドレスに関する知識を豊富に実装しているから、ドメインリッチ。

・形式が、RFC822 に適合しているかをチェックする validate() メソッド。
・複数のアドレスの文字列表現を読み取る parse() メソッド。
・RFC822 準拠の形式で、 unicode 文字列で表現するための フォーマットメソッド。

InternetAddress クラスが、メールアドレスに関する豊かな知識の置き場所になっている。

住所の PostalAddress クラスとか、電話番号の PhoneNumber クラスとか、同じようなネタはいっぱいありますよね。 住所についての知識、電話番号に関する知識を、同じクラスのメソッドに実装するのが、ドメインリッチな設計。

Money クラスも、値を持つだけでなく、千円単位とか百万円単位とかの表現の知識を、 parse() と format()で実装する。

もちろん、金額計算も、add() とか、multiply() とかを実装する。 BigDecimal よりも、Money のほうが、ずっと、ドメイン駆動のオブジェクトになる。

ここらへんのドメインリッチの感覚をチームで共有すれば、ドメインクラスの設計が見違えるほどドメイン駆動になる。

とりあえず、validate() 、parse(), format() メソッドあたりから始めると、わかりやすいのかな?

XP で、XML やテーブルはどうなるんだ?

最近、私が関わっているシステムは、外部システムとの連携が重要課題。
実装技術は、 XML over HTTP 、まあ、単純に XMLファイルを HTTP で送ったり、受信する。

ドメインモデルを元に、

・ Java
・ XML
・ テーブル

を設計して、実装している。 Java と テーブル定義のDDL は、モデルからコード生成している。
XML は、モデルを参考に、直接コーディング。(ちょっと怪しい)

モデルがあって、複数の言語で、実装する、というのは教科書的(?)にはとても正しい。

で、ふと思ったのが、XP では、どうやっているんだろう。
あるいは、テスト駆動開発でも良いのかな?

まわりに、実践している人がいないので、本やインターネットの情報から推測するしかないんだけど、テスト駆動で、Java のクラスを作るのはなんとなくわかるけど、XML定義や、テーブル定義は、いったいどうやっているんだろう。

Java のソースコードが設計で、それを元に、XML定義やテーブル定義をやるの?
なんかちがう気がする。

視点と観点

設計の基本は、関心事の分離。

アーキテクチャのスタイルブック、モデリングのパターンカタログ、実装のパターンカタログ、どれも、良い設計の参考情報。

つまり、うまい関心事の分離の仕方のサンプル集。
パターンカタログ自体、いろいろな関心事が集まっているので、ぴったりのパターンを探すのが結構たいへんだったりする。かといって、頭から順番に読むのはしんどいものが多い。

面白いと思ったのは、

システムアーキテクチャ構築の原理 

この本は、

View Point (視点)
Perspective (観点)

で縦横のマトリクスを作って、設計の関心事の整理をしている。

視点は、
・機能
・情報
・並行性(同時性)
・開発
・配置
・運用

主な観点は、
・セキュリティ
・処理性能
・可用性
・発展性
...

視点

一点に集中して目を凝らすイメージ。
いろいろな関心事のうち、特定の範囲だけに絞って考える。
現在の視点以外の関心事はとりあえず無視する、というアプローチ。

機能と情報を別視点として分離するのは、危険なアプローチでもある。
いわゆる部分最適に陥りがち。

でも、ものごとを整理するには、ある瞬間は、視野を限定して、特定の視点だけで検討するのは、システム開発の現場ではとても効果的。

「ある瞬間」というのがポイント。毎日の業務の中でも、いろいろな視点を時間で切り替えながら使い分けることでがたいせつ。


観点

広く見る、ということ。
全体像を見ることは複雑なシステムでは、とてもたいせつ。
いろいろな視点(部分を見る)を統合する道具が観点というわけだ。

ただ、全体を見ていると、人によってイメージや受ける印象がすごく違ってしまう。
同じ場所を観光していきても、印象に残っているところはみなさんばらばらですよね?

広く見ることを構造化することが観点(Perspective)の重要な点。

絵画の遠近法の手法を使う。

遠近法では、近いところから遠くまでの全体を「消失点」に収束するように放射状の描く。

さまざまな視点の関心事を、たとえば、セキュリティという消失点に収束するように並べる感じ。

セキュリティという消失点(収束点)で遠近法で描くとと、たとえば、情報の視点のいろいろな関心事も、個人情報はかなり前面にあるし、まあ、外部に漏れてもよいような情報ほど、消失点によっていく。(小さくなっていって、最後は見えなくなる)

視点と視点の間でも、情報を前面に持ってきて、配置はだいぶ遠くにある(消失点によっている)というような整理ができる。

狭く見る視点、と、広く見る観点をうまく組み合わせることで、複雑で込み入った関心事を、整理するためのテクニックを手に入れることができるわけだ。

絵画も、遠近法が当たり前になるのは、結構最近のこと。誰でもできることではなかった。
情報システムの場合、たぶん、いろいろな方法論は、こういうことを試みているんだろうけど、なかなかわかりやすい遠近法がないですよね。

このシステムアーキテクチャ構築の原理は、いままで読んだ本の中では、いちばん使えそうな遠近法だと思っている。

大きな一枚岩のクラス vs. 小さなクラスのチーム

大きな一枚岩のクラスと小さなクラスのチーム


一つの大きなクラスにまとめるか、情報をもうちょっとグループ分けして、小さなクラスに分けるか?

ドメイン駆動設計は「小さなクラス」に分ける。

いや、ドメイン駆動設計だけじゃない。オブジェクト指向設計では、というか、ソフトウェアの設計の基本原則が、関心事の分離。とにかく分ける。

左は、大きな一枚岩の会員クラス。

会員を識別する情報(Entity) 、連絡するときの情報(Value)、住所の情報( Value ) という三つの関心を一つのクラスに押し込でいる。
典型的なアンチパターン。
「会員に関連する」というだけで、会員がらみの情報(属性)がこの入れ物に次々と追加され、肥大化していく。
検証ロジックや分類ロジックも膨らんできて、あっというまに見通しの悪い密林クラスの誕生。


右は、小さなクラスに分けて、それを1チームとして連携するパターン。
これだと、住所の持ち方や使い方を考えるときに、連絡先とか、会員番号とかの心配をしなくて良い。
ロジックも、住所に関するロジック、連絡先に関するロジックと、自然に分割整理できる。
図ではクラスにしていないけど、電話番号とか、Eメールアドレスも当然クラスです。( String 型ではなく、EmailAddress 型とか PhoneNumber 型 )

電話番号の入力形式や、表示形式の知識は、PhoneNumber クラスの parse() メソッドや、format() メソッドに収める。

で、これらの小さなクラスが集まって、「会員」を表現していることを、関連でモデリングする。

一枚岩のクラスよりも、豊かにドメイン知識を議論して、モデリングできる。

ドメイン駆動設計の第一歩や、こうやって、関心事ごとにクラスを分ける。関係する事柄は、関連で明示すること。

画面では、だいたい、小さなクラスの単位で、枠線を引いたり、背景色を工夫して、情報のグルーピングを明示していますよね。
その表示の構造と、クラスで表現する構造は一致しているべきなんです。

とても、簡単なことなんだけど、私にとっては、実はコペルニクス的転換というか、大きなパラダイムシフトでした。大きな一枚岩クラスでテーブルやクラスを作ることが当たり前だと思っていた。
小さく分割する、という選択肢はまったく持っていないかった。

分割することに慣れちゃうと、とても、昔の大きな一枚岩クラスにもどれないです。
問題を、小さな単位に分割すると、一見、面倒なようで、実際は、とても楽になる。

小さな問題は、すぐ解決できちゃうので、気が付くと、たくさんの問題を短時間に解決できちゃう、ということを何度も経験している。
関心事の分離は、問題解決のちょっとしたマジックです。


calendar
   1234
567891011
12131415161718
19202122232425
2627282930  
<< April 2009 >>
システム設計日記を検索
プロフィール
リンク
システム開発日記(実装編)
有限会社 システム設計
twitter @masuda220
selected entries
recent comment
recent trackback
categories
archives
others
mobile
qrcode
powered
無料ブログ作成サービス JUGEM