実践 Domain-Driven Design : リポジトリの具体例

DDD 7章 続き 172ページ。

集約は、オブジェクトの保管・取り出しの単位である。そのためのメカニズムとしてリポジトリ(REPOSITORY)パターンを検討する。

7章の例では、顧客貨物場所運送イベント輸送イベントの5つが候補。
ENTITY で、かつ集約のルートと決めたオブジェクトはすべてリポジトリ作成の候補。

リポジトリはドメイン層のオブジェクト。必要性・妥当性を検証するためにアプリケーション(ドメインの利用者)の視点で検討する。

顧客(Cuctomer)リポジトリ

予約アプリケーションで、顧客をリポジトリから探すことが必要。
探し方は、リポジトリクラスのメソッドで定義する。

find By Customer ID
find By name
find By Cargo Tracking ID

の三種類が役に立ちそう。

場所(Location)リポジトリ

予約アプリケーションで、貨物の送り先を指定するために場所を探すことが必要。

find By port code
find By city name

輸送(Carrier Movement)リポジトリ

配送活動記録アプリケーションでは、貨物を送るための輸送をリポジトリから探すことが必要。

find By Schedule ID
find By From To

貨物(Cargo)リポジトリ

配送活動記録アプリケーションで、貨物を指定する必要がある。

find By Cargo Tracking ID
find By Customer ID

運送イベント(Delivery Event)リポジトリ

関連を検討した時、配送履歴運送イベントのコレクションで実装する方針にした。
この方針だと、運送イベントをリポジトリから取り出す操作はない。つまり、リポジトリは作成しない。
(後で方針を再検討するらしい)

---

なるほど。
問題領域でどんな検索が必要か、やりたいのかを、リポジトリとそのメソッドで定義するのか。

リポジトリと検索メソッドを検討すると、このソフトウェアで何を実現するかが具体的になる。

7章のモデリングのステップをまとめると、

1.アプリケーションクラスとドメインクラスを分離
2.(ドメイン層で)ENTITY と VALUE OBJECT を識別
3.関連とその方向を検討
4.集約の境界を検討
5.リポジトリと検索メソッドを検討

という5ステップ。

たしかに、4章のレイヤ・アーキテクチャ、5章の ENTITY, VALUE OBJECT、6章の AGGREGATE と REPOSITORY の具体例になっている。

モデリングの時に、具体的に検討すべき項目と選択肢、実践的な判断ルールが7章ではいろいろ出てきた。

モデルを検討する時、アプリケーションの視点、対象業務(問題領域)の視点を繰り返し使うことが印象的。
これがドメイン駆動の設計というわけですね。

配送の一般的なモデルではなく、国際配送、それも配送状況の追跡という問題領域にフォーカスする。
問題領域を深く検討すると、モデル作成の選択肢に、具体的な判断基準があることが理解できた。
モデルの違いが、コレクションの使い方とか、データベースアクセスとか、実装に影響することも理解できた。

実践 Domain-Driven Design : 集約の境界とアンチパターン

集約の境界を使わないと、どんなことになるだろう?

オブジェクトの海

とにかく小さなクラスやテーブルに分割する。関心の分離として正しい方向。
でも、ちょっとしたアプリケーションでもすぐクラス数やテーブル数が100を超える。変更のたびに数が増え、やがて誰にも扱えなくなる。
ひとつひとつのオブジェクトの実装は単純になるけど、全体の構造が誰にもわからない。

巨大な一枚岩(エアーズロック)

だったら、関連するものをひとつのオブジェクトにまとめたら、どうなるだろう?
顧客オブジェクトが20も40も属性を持つ。ひとつのクラスに大量の get/set メソッドをコーディングする。
テーブルも属性数が100を超えるほど膨らんだりする。

そして、変更のたびに膨らみ、やがて誰にも扱えなくなる。

回避策

どちらも理解が難しく、変更が難しい、よく見かけるアンチパターン。

DDD の集約(AGGREGATE)は、このアンチパターンを回避する実践的なテクニックですね。

関心を分離して小さなオブジェクトに分割するけど、関連するオブジェクトはひとつのかたまりとして一括して扱う。

単なる集約モデリングでは意味がない。
実装する時、集約の境界内のオブジェクトの可視性と操作の制限を徹底することがポイント。

プログラムの構造を整理できて、わかりやすく、変更も容易になる。
その集約構造は、問題領域の分析で発見したドメインの知識を反映している点が価値ですね。

実践 Domain-Driven Design : 集約の境界 具体例

DDD 続き 170ページ。 集約の境界(続き)

集約(AGGREGATE)パターンを具体例で検討する。

明らかに集約のルートであるオブジェクト:
顧客
場所
輸送

理由:
・識別番号を持つ独立した ENTITY である。
・いろいろな貨物が共有する ENTITY である。

貨物も、もちろん集約のルートである。

この貨物をルートとする集約の境界内には、
配送履歴
配送仕様
運送イベント
を集めることができそう。三つとも、特定の貨物のためだけに存在するオブジェクトだから。

配送仕様はもともと VALUE OBJECT だから、集約に含むのは問題ない。

配送履歴も、貨物を無視して、単独で取り出すことはありえないから、これも集約に含むのが当然。

問題は運送イベント

運送イベント
は、輸送とも関係する。貨物輸送とを関連づけているオブジェクト。
貨物の側からだけなく、輸送の側からもアクセスの対象になる。

例えば特定の輸送に関係する運送イベントを一覧する、ということがありそう。
運送イベントは、貨物とは関係なく扱うこともありえる。

こういう多面性のあるオブジェクトは、無理やりどちらかのオブジェクトの集約の境界内に含めるのではなく、独立した集約のルートとすべき。

結論:

貨物の集約が含むのは、配送履歴配送仕様
運送イベントは独立した集約とする。

---

モデル上のオブジェクトをすべてばらばらに扱えば、設計や実装は単純になる。
ただし、集約で表現できる問題領域の知識を見落とすことになる。

集約は、オブジェクトの可視性・操作の権限を制約する。この制約は、問題領域(ドメイン)を特徴づけるビジネスルール。このビジネスルールを発見し、実装することを重視するのがドメイン駆動設計というわけだ。

それが、問題領域の知識が豊かな、役に立つソフトウェアを作る秘訣だから。

実践 Domain-Driven Design : 集約の境界

DDD 7章 続き。170ページ。集約の境界 (AGGREGATE Boundaries)

ここまでは、Entity を識別し、クラス間の関連を識別する作業の説明。
次のステップは、クラスをひとかたまりで扱う AGGREGATE (集約)の具体的な検討に進む。

集約の意味

UMLクラス図では、AGGREGATE(集約)は、白いひし形(◇)で示す。UMLを使うようになっても、正直、このシンボルの意味がぴんとこなかった。

Domain-Driven Design を読んで最初に印象的だったのが、この集約(AGGREGATE)パターンの考え方と説明。

このパターンを例えるために「ぶどうの房」の写真を使っていた。
クラス図に手書きの黒の太線で、関連するクラスを囲っていた。

関連するオブジェクトを「ひとかたまり」でとらえる、という考え方を直感的に表現している。

集約の実装

境界の外側のオブジェクトは、集約のルートクラスだけを参照できる。
外部のオブジェクトは、集約の境界内の他の Entity オブジェクトを参照(所有)できない。
外部のオブジェクトは、集約の境界内の Value Object を所有できない。必要ならコピーを渡す。

集約の境界は、可視性と操作の権限を明確に定義する。
この実装ルールの説明を読んだ時、ほんと、目から鱗が落ちました。

集約は単なる関連の一種ではなく、オブジェクトの見せ方、扱い方を制限することなんだと。

集約を使うモデリングの効果

集約の境界は、問題領域の構造として、ドメインオブジェクトの可視性/操作の権限を定義する。
(実装の Java クラス/パッケージの可視性制御よりもっと概念的)

集約の境界単位で、オブジェクトを操作する。境界内部のオブジェクトは隠ぺいして、保護する。

この考え方を理解しドメインモデリングをするようになってから、クラス図の集約シンボル(◇)が突然、輝きを増した。
集約の境界は、問題領域を深く検討し理解するために、実践的で効果的な手段ということが実感できた。

個人的には、集約の境界を理解した時が、ドメイン駆動設計の世界への扉が開いた瞬間だったと思う。

実践 ICONIXプロセス : ロバストネス図という魔法

ロバストネス分析は予備設計です。

実装の設計(シーケンス図)を行う準備作業として、ロバストネス分析を行う。

要求は、以下のモデルで記述します。
・ドメインモデル(概念クラス図)
・ユースケースモデル(パッケージ図、ユースケース図、ユースケース記述)
・補足仕様書(画面の紙芝居/線画/モックアップ、ビジネスルール詳細など)

この情報を完璧に理解して、設計ができればすばらしい。

でも、現実は、設計をはじめてから発覚する要求仕様のモレ、矛盾、誤解はかなり多い。

ICONIXプロセスは、この現実の問題を解決する実践テクニックとしてロバストネス図を使う。

ロバストネス図の二面性

ロバストネス図は、ユースケースを絵にしただけだから、要求モデルの一部です。
しかし、
ロバストネス図のバウンダリとエンティティは、シーケンス図のオブジェクトになり、コントロールはシーケンス図のメソッドになる。つまり、実装の設計図です。

「要求」モデルであり「実装」モデルでもある。このロバストネス図の二面性が、モデルを直接コードに持っていくマジックの種明かしです。

プログラマはシーケンス図までは、自分の領域に感じるようです。もう一歩だけ、要求に近づいて、ユースケースを読んでロバストネス図を描くのは、実装設計の準備なので違和感なく取り組めるはず。


要求定義も実装の一部

ロバストネス図は、ユースケースを絵にしただけ。ロバストネス図を描けるということは、自分で、ユースケースを書くこともできるということ。ここで、さらに一歩、要求に近づく。

ICONIXプロセスを社内に導入してから、一番大きな変化は、開発者たちが、要求仕様のモデル化(ユースケース記述)を、実装に近い作業として取り組むようになったこと。それも、いわゆる上流工程の概念的なあいまいな作業ではなく、開発作業の一部、実装の準備作業という感覚でやっている。

実際、Eclipse を使う時間が減り、Enterprise Architect を使う時間、図やユースケース記述を使って、メンバーどうしや顧客と話す時間が増えたのはびっくりします。

この変化の効果は絶大で、顧客の要求の取り違い、とんちかんな実装、とんでもないバグがほんとうに減りました。要求を勘違いをしていても、早い段階に発覚するし、軌道修正も速い。

コード : 変数の直接アクセス

Kent Beck の書籍から。

ガレージのドアを制御するプログラムを書く場合、どれが読みやすいか?

doorRegister = 1 ;

doorOpen() ;

door.Open() ;

もちろん、下に行くほど「やりたいこと」をうまく表現している。
制御ビットに1を立てるという実装の詳細より、ドアを開ける、という意図を表明したコードの方が読みやすい。
もちろん、実装方法の変更にも強い。

基本は、直接アクセスより、メソッドで間接アクセスにして、メソッド名に意図を表す名前をつけるのが良い。

どの範囲で x=1 という直接アクセスの記述をOKとするか、いろいろ選択肢がある。

・アクセサメソッドの内部とコンストラクタの内部に限定する (最も限定するパターン)
・クラス内はOK
・サブクラスまでOK
・パッケージ内はOK
...

Kent Beck は、「一般的なルールはない。自分で考え、まわりと議論し、勉強しろ! それがプロになっていくということ」と書いていますね。 ( Implementation Patterns p.46 )

私は、できるだけ制限する最初のパターンがベストだと思っていました。
同じクラス内でも、アクセサ以外は、アクセサを使うというパターンですね。

でも、ドメイン駆動を勉強するようになってから、わりと小さなクラスを作るようになり、「クラス内は直接アクセスOK」派に転向しました。

x=1 式の記述は、直接的で分かりやすい。同じクラス内で、わざわざアクセサを使うより、理解も速いし、まちがいが少ないと思います。でも、小さなクラス限定です。

大きなクラスを、意図が明確な複数のクラスに分割していくリファクタリングでアクセサを活用しています。
でも最終形は、クラス内では直接アクセスにする。

1.アクセッサメソッド式にして、できるだけ間接アクセスにする。
2.関連が強いフィールドとアクセサをまとめて小さなクラスに抽出する。
3.抽出したクラスに割り当てることが適切なメソッドを、そちらに移動する。
4.移動したメソッド内でアクセサを使っていたら、フィールドの直接アクセスにインライン化

という感じです。

実践 ICONIXプロセス : ロバストネス図にユースケースを置く

ICONIXプロセスのユースケースモデリングでは、汎化(generalize)、包含(includes)、拡張(extends)を使いません。
分析・検討に時間がかかるのに、何の役にも立たない。

ICONIXプロセスでは、ユースケースを、ユーザマニュアルと同じ、と考えています。

・汎化したマニュアル(説明)が読みやすいですか?
・共通部分の説明を包含ですませた説明が読みやすいですか?
・拡張部分だけを別に記述した説明が読みやすいですか?

すべての答えはノー。だから、使わない。

もちろん、共通部分がありますので、

・先行 ( precedes ) : ユースケースを順番に実行する
・呼出 ( invokes ) :サブのユースケースを呼出(実行後、元に戻る)

の2つのパターンは積極的に使います。

ひとつのユースケースは単純なほうがわかりやすいので、ICONIXプロセスでは、ユースケースは、わりと小さな単位になります。

ユースケースを絵にした時、ロバストネス図が、普通にA4に収まるくらいのイメージ。

あるユースケースをロバストネス図として描いているとき、

・先行するユースケース
・呼出元のユースケース
・呼び出すユースケース

をユースケースモデルからドラッグしてきて、ロバストネス上に置きましょう。

そのユースケースをどういう文脈(コンテキスト)で実行するのか、分かりやすくなります。

最初のうちは、ロバストネス分析をしながら、ユースケースモデルの構造をいろいろ改良することが多い。

・ユースケースを分割する
・ユースケースの先行、呼出関係の見直し
・ユースケース名の変更

などですね。

これでユースケースモデルの構造が、強靭(ロバスト)になる。

実践 ICONIXプロセス : ロバストネス図の矢印の方向

ロバストネス図の目的

・ユースケース記述から曖昧さを除く。
・ドメインモデルで見逃したオブジェクトを発見する。

つまり、主体は、ユースケース記述であり、ドメインモデルです。

・ユースケース記述を改良して強靭(ロバスト)にする
・ドメインモデルを改良して強靭(ロバスト)にする

ための手段がロバストネス図、ということですね。

ユースケース駆動開発実践ガイドでは、ロバストネス図の矢印の方向は、重要ではない、といっています。
矢印の方向は、上記の目標の達成には重要ではないと。

ただし、矢印はデータの流れ、制御の流れを示す、便利な手段です。
ロバストネス図を描くときは、矢印を積極的に使ったほうが、わかりやすくなります。

誰でもぱっと見て理解できるように
・制御の順番
・情報の流れる方向
だけを意味することを、チーム内で徹底しておくことがたいせつ。

まちがっても、UMLのコミュニケーション図の矢印の方向の議論を持ち込まないでください。
ロバストネス図は、ロバストネス図と割り切って使いましょう。

補足メモ

UMLは基本的にオブジェクト指向の「メッセージ」の概念を重視する。

例えば、get() メソッド。
・メッセージの方向は、 get()を使うクラス → get() を実装したクラス、になる。
・情報の流れる方向は、 get()を使うクラス ← get() を実装したクラス、と逆になる。

コミュニケーション図は「メッセージ」による方向を明示する手法です。オブジェクトとオブジェクトが「メッセージ」で、コミュニケートする様子をモデル化する手段。

ロバストネス図は、制御の順番情報の流れを表すために矢印を使う。

ユースケース駆動開発実践ガイドでは「矢印の方向は重要ではない」といっていますが、実践的には、制御の順番情報の流れを表すために矢印を積極的に使うほうが、良い分析が簡単にできると思います。

実践 ICONIXプロセス : コントロールとコントローラクラス

ICONIXプロセスのロバストネス分析ではコントロールは、機能(ロジック)の置き場所(プレースホルダ)。
実装時のコントローラクラスではない。

名前がかぶっているので、実装時のコントローラクラスをマネージャクラスと呼ぶことにしましょう。
(○○Manager)

ロバストネス図のコントロールのアイコンは、実装時には、メソッドになります。(クラスではありません)

もちろん、マネージャクラスが必要な場面もある。
「ユースケース駆動開発実践ガイド」であげている例:

コントロールのかたまり

多くのコントロールは、表示/妥当性検証/情報取得/情報書き込みなど、独立した機能です。
しかし、関連するコントロールのかたまりをロバストネス図上に発見することがあります。

この場合は、そのかたまりをマネージャクラスとして実装する、という設計はありです。

状態遷移

ひとつのユースケースの実行時に状態遷移の管理が必要な場合。
具体的には、ユースケースを書くために、状態図でモデリングをしてみた、というような場合。

この状態遷移を管理するためにはマネージャクラスを実装することが適切そうです。
もっとも、状態管理を意識する必要があるユースケースが多いとは思えません。

マネージャクラスの多用の戒め

ICONIXプロセスの筆者たちは、設計や実装でマネージャクラスやコントローラークラスを使いすぎないように警告しています。

ロジックだけをひとかたまりにしたクラスが有用なシーンはもちろんありますが、原則は、ロジックとデータをひとかたまりにすること。現場の技術者たちが、この原則を忘れて、マネージャクラスを多用しがちなことを、筆者たちは、トレーニングの現場で痛感し、また危機感を持っているようです。

変更率 ( Rate of Change )

Kent Beck の 読みやすいコードの6原則の最後。

同じタイミングで変更するデータ、関連するロジックは、いっしょにすべき。

具体例:(書籍から)

金額を通貨単位といっしょに扱う例ですね。

setAmount( int value, String currency )
{
 this.value = value ;
 this.currency = currency ;
}

value と currency はいつもセットだからいっしょにすると分かりやすい。
Money オブジェクトを作る。

setAmount( int value, String currency )
{
 this.value = new Money( value, currency ) ;
}

パラーメータも2つはいっしょなので Money オブジェクトを使う。

setAmount( Money value )
{
 this.value = value ;
}

最初のサンプルコードでは、value と currency の関連性は、人間が判断する。コード上はばらばらに見える。

最後のサンプルコードでは、value と currency を Money にカプセル化し、かつ、詳細を情報隠蔽している。

int value
String currency

がコードに散在していたのを

Money value

にひとまとめにする。

これで、
・意図が明確になった (コードが業務つまり問題領域の用語になった)
・あちこちに同じコードを書かなくてよくなった
・通貨に関する知識を Money オブジェクトローカルにカプセル化できた

Kent Beck の「読みやすいコード」の原則とその具体例です。

calendar
     12
3456789
10111213141516
17181920212223
24252627282930
31      
<< August 2008 >>
システム設計日記を検索
プロフィール
リンク
システム開発日記(実装編)
有限会社 システム設計
twitter @masuda220
selected entries
recent comment
recent trackback
categories
archives
others
mobile
qrcode
powered
無料ブログ作成サービス JUGEM