ObjectSquare [2007 年 8 月号]

[技術講座]


DDD難民に捧げる
Domain-Driven Designのエッセンス
第2回 DDDの基礎と実践

株式会社オージス総研
アドバンストモデリングソリューション部
佐藤 匡剛

Domain-Driven Design の表紙

Domain-Driven Design
Tackling Complexity in the Heart of Software

Eric Evans 著
Addison-Wesley, 59.99ドル
560ページ
ISBN: 0-321-12521-5

本連載は、全3回の予定でEric Evansの書籍『Domain-Driven Design』(以降DDD)を紹介しています。前回はDDDの概要を説明し、第I部「Putting the Domain Model to Work」からDDDの基本原則となる3つのパターンを紹介しました。今回は続く第II部と第III部から、(アンチパターンを1つ含む)16のDDDパターンをカタログとして紹介します。

第II部「Building Blocks of a Model-Driven Design」(モデル駆動設計の基本要素)では、第I部で登場したモデル駆動設計(MDD)パターンを実現するための、基礎的なモデリングの考え方が説明されています。そして第III部「Refactoring Toward Deeper Insight」(さらなる洞察へ向けたリファクタリング)では、ドメインモデルを表層的なものからドメイン知識を反映したものへと深化させていく「ドメインリファクタリング」の考え方が提示され、リファクタリングを実行する上で役立つ実践的な設計ノウハウが紹介されています。

DDDの主要な設計思想を語っているのが、この第II部と第III部です。前回の説明だけではまだ具体的なイメージが湧かなかったかもしれませんが、今回の16パターンによって、DDDが実際にどんな設計思想であるかがよく分かるでしょう。

II. Building Blocks of a Model-Driven Design(モデル駆動設計の基本要素)

第II部では、DDDの要であるMDDを実現するための基礎が説明されています。ここでは、いわゆるオブジェクト指向設計のベストプラクティスが、ドメインモデリングの観点からまとめ直されることになります。

DDDは、既にあるオブジェクト指向設計の原則やプラクティスと矛盾するものではありません。著者自身が書籍で述べているように、DDDはRebecca Wirfs-Brockの「責務駆動設計(Responsibility-Driven Design)*1や、Bertland Meyerの「契約による設計(Design by Contract)」を背景にもっています。また、Craig LarmanのGRASPパターンなど、広く知られた他のオブジェクト指向設計のプラクティスとも一致します。

DDDをMartin Fowlerの『エンタープライズアプリケーションアーキテクチャパターン』(PofEAA)経由で知った方も多いかもしれませんが、第II部はこのPofEAAとも非常に関連が深い部分です。PofEAAでお馴染みのパターンが、ここでもいくつか登場します。

*1 ちなみに、このRebecca Wirfs-Brockの書籍『Object Design: Roles, Responsibilities, and Collaborations』の邦訳(邦題『オブジェクトデザイン』)が今年の9月上旬に出版予定です。私も一査読者として翻訳に協力させていただきました。このオブジェクトの広場にて、近々改めて書籍紹介をしますのでぜひお楽しみに。

ドメイン層の分離

ドメインモデルとプログラムコードとの緊密な連携のためには、まず何よりもドメイン層を他の関心事(プレゼンテーション、データアクセスなど)から分離する必要があります。

● Layered Architecture(層状アーキテクチャ)パターン

ビジネスロジックがユーザインタフェース(UI)、永続化などのロジックと一緒になっていると、UIの些細な変更がモデルに影響したり、モデルを修正するために低レベルなデータアクセスのコードをいじらなければならなくなったりする。ドメインモデルとコードとを結びつけるMDDを実践するには、ビジネスロジックがUIやデータアクセスのコードから切り離されていなければならない。ソフトウェアを以下の4層に分けることで、純粋かつ明確なドメインモデルをドメイン層の中に構築することができる。

  • UI層 … ユーザとの相互作用の境界となる層
  • アプリケーション層 … ドメインオブジェクトを操作することで、ソフトウェアが果たすべき仕事を実現する層
  • ドメイン層 … ビジネス上の概念を表現する層
  • インフラストラクチャ層 … 上の3層を支える技術的な基盤となる層

● Smart UI(利口なUI)アンチパターン

層状アーキテクチャの対極をなすアンチパターン。ビジネスロジックやデータアクセスのコードが、UIのコードと一緒になってしまっている、いわばスパゲッティな状態。利口なUIと呼ぶのは、ビジネスロジックを含むすべての処理がUIの中で行なわれるから。最もやっつけで手軽なやり方がこれなので、設計を何も考ないとこの状態に陥ってしまう。開発チームのスキルが低くて、かついわゆる第4世代(4GL)と呼ばれるようなグラフィカルな開発ツールを最大限に利用する場合は、このパターンの採用にも一理ある。しかし、利口なUIを採用してしまうとMDDが一切機能しなくなるので、DDDは諦めるしかない。

ソフトウェアによるモデルの表現

ドメインモデルの構成要素は、大きく「エンティティ」「値オブジェクト」「サービス」の3つに分類できます。また、それらをまとめる「モジュール」も、ドメインモデルの重要な構成要素になります。

● Entities(エンティティ)パターン

オブジェクトの中には、アイデンティティをもつもの(エンティティ)が存在する。エンティティは、アイデンティティが同じならば、属性が異なっていても同一のものとして扱わなければならない。逆に、属性がまったく同じでも、アイデンティティが違えば当然別エンティティとなる。エンティティはドメインモデルの主役であり、そのライフサイクルを考えることが重要になる。ドメインモデルの中において、エンティティの同一性をどうやって判断するかを定義しなければならない。

● Value Objects(値オブジェクト)パターン

エンティティとは逆に、たとえば「色」や「量」のように、その属性だけが重要で、アイデンティティを考えることに意味のないオブジェクトもある。そうしたオブジェクトは、値オブジェクトに分類する。値オブジェクトとは、事物の性質を表現するものである。値オブジェクトは状態を変更できないもの(immutable)として扱う。エンティティの複雑さに専念できるように、値オブジェクトはシンプルな設計に保つようにする。

(この値オブジェクトは、PofEAAのValue Objectパターンと同じものを表している)

● Services(サービス)パターン

ドメインで扱う概念の中には、1つの機能や処理が単体で存在していて、もの(オブジェクト)として扱うのが不自然なものもある。そうしたものは、サービスという形でユビキタス言語に組み込む。サービスは基本的に状態をもたない(stateless)。

(PofEAAのService Layerパターンとは異なる概念なので注意。Service LayerパターンはDDDのアプリケーション層に相当するものを言っているが、DDDのサービスはドメインモデルの中にあるサービス的なものを指している。邦訳はないが、Fowler氏の記事「Evansの分類」も参考になる)

● Modules(モジュール)パターン

モジュールの仕組み(Javaのパッケージや.NETの名前空間など)は、プログラミングにおいては誰もが使っている。人間が一度に考えられる物事の量には限界があるので、概念についても同じようにモジュール分割が必要である。プログラミングの場合と同じく、ドメインモデルのモジュール分割においても高凝集/低結合が重要な目安になる。関連し合う概念同士はひとつにまとめ(高凝集)、かつ一度に考えなければいけない範囲は最小限にする(低結合)。モジュールの名前も、ユビキタス言語の重要な一要素として扱う。モジュールもコードと同様、リファクタリングによってアジャイルに進化させていかなければならない。

ドメインオブジェクトのライフサイクル

オブジェクトには、生成から消滅までのライフサイクルがあります。またその生存期間中に、一時的にデータベース(DB)上に保存されたりもします。こうしたオブジェクトのライフサイクルも、ドメインの設計に組み入れなければなりません。

● Aggregates(集約)パターン

オブジェクトのライフサイクルを設計するには、まずライフサイクルの単位となるオブジェクトのまとまりを考えなければならない。たとえば「注文」と「注文明細」のように、関連するオブジェクトのグループは集約として扱い、集約を単位としてモデルの他の要素との境界を明確に分ける。集約の中から1つエンティティを選んで、それを集約のルート(root)とする(先ほどの例では、「注文」がルートになる)。1回きりの処理で参照が破棄されるような特別な場合を除いて、外部から参照できるのはルートだけで、中のオブジェクトに対する処理はすべてルートが中継する。こうすることで、集約内のモデルの一貫性を維持することができる。

● Factories(ファクトリ)パターン

オブジェクトや集約の生成処理はそれ自体複雑になりうるため、ファクトリを導入して生成処理をカプセル化*2する。オブジェクトの生成そのものがドメインモデル上で重要な意味をもつことは(ほとんど)ないため、ファクトリはドメインモデルの一部ではない。あくまで、ドメインの設計上必要な一要素、という位置付けになる。

*2 本来「カプセル化」とは単に属性や操作を1つにまとめることを表す概念で、実装の詳細を外部から隠す概念である「情報隠蔽」とは異なります。しかしEvansはこの2つを混同して使っていて、カプセル化という言葉を情報隠蔽の意味でも使っていることに注意してください。

● Repositories(リポジトリ)パターン

ファクトリにより生成されたドメインオブジェクトは、役目を終えて破棄されるまでは生存する。生存期間の途中で、ほとんどの場合はDBなどにいったん永続化される。DBへの永続化や問い合わせ処理の複雑さによって、ドメインモデルが汚染されないようにするため、リポジトリという永続化/問い合わせ専用オブジェクトをドメイン設計に導入する(ファクトリ同様、ドメインモデルの一部ではない)。リポジトリを使う側からは、ドメインモデルがあたかもメモリ上にコレクションとして存在しているかのように見える。リポジトリは、モデル中でグローバルなアクセスが必要なエンティティ集約の場合はそのルート)毎に、1つ用意される。

(このリポジトリは、PofEAAのRepositoryパターンと同じものを表している)

III. Refactoring Toward Deeper Insight(さらなる洞察へ向けたリファクタリング)

ここまでで、ドメインモデル構築の基本的な道具が揃いました。しかし、ドメインの専門家やユーザの頭の中にある知識をより良く反映した「深いモデル」(deep model)を得るには、継続的な改善が必要です。DDDのモデリングは、「名詞がクラスになって動詞がメソッドになる」というような、古典的な単純化されすぎたOO方法論とは異なります。優れたドメインモデルの設計に到達するには、イテレーションを繰り返さなければならないのです。

著者Eric Evansは、リファクタリングを次の3つのレベルに分類しています。

最初の2つは、技術的な観点から設計の質を高める従来のリファクタリングです。つまり、コードが「何をするのか(What)」を分かり易くするものです。一方DDDが提唱するドメインリファクタリングとは、ドメインに対する深い洞察をモデルに反映させるためのリファクタリングです。コードが「何故そうなっているのか(Why)」まで明らかとなるように、コードとモデルとを改善していくのです。

絶え間ないドメインリファクタリングを可能にするのが、コードの変更を容易にする「しなやかな設計」(supple design)です。MDD実践の真髄は、この「深いモデル」と「しなやかな設計」の2本柱によって成り立つのです。

*3 『パターン指向リファクタリング』の著者Joshua Kerievsky氏は、DDDをAlexander形式のパターン本にするよう促した張本人でもあるそうです(DDDの謝辞を読んでみてください)。

深いモデル ― 隠れた概念の発掘

「深いモデル」を得るには、ドメインに隠された概念を見つけ出す必要があります。見つけづらい概念には、経験上「制約」「プロセス」「仕様」に関するものがあります。*4

*4 仕様パターンは、元々Eric EvansがMartin Fowler氏との共著で、1997年のPLoP(Pattern Languages of Programs)カンファレンス(ソフトウェアパターンの国際会議)において発表したものです。ここで1つだけパターンを取り上げているところに、著者のこのパターンへの思い入れを感じます。

● Specification(仕様)パターン

isXxx()のような真偽値を返すメソッドからなるビジネスルールは、通常のエンティティ値オブジェクトには上手く割り当てられない。ビジネスルールをドメインモデル上で表現するには、論理学でいう「述語」(predicate)のような役目をする特殊な値オブジェクトを導入する。これがすなわち仕様である。仕様は一般に、次の3つを規定する。

  1. オブジェクトの妥当性検証(validation)
  2. オブジェクトの選別条件(selection)
  3. オブジェクトの生成条件(creation)

しなやかな設計 ― 理解の容易な抽象化

「しなやかな設計」の要点は、抽象化と分割の2つです。抽象化において大事なことは、モデルを見ただけでその振る舞いが容易に理解できるようにし、カプセル化の価値を最大限に高めることです。

● Intention-Revealing Interfaces(意図の明白なインタフェース)パターン

オブジェクトの内部実装を見なければ正確な使い方が分からないようでは、カプセル化が正しく機能しているとは言えない。意図が分かりにくければ、誤った使い方を誘発することにもなる。クラスやメソッドの名前というのは、開発者同士がコミュニケーションするための絶好の場所である。クラスやメソッドには、どういう方法で実装されているかでなく、その意図を示した名前を付けること。その名前が、ドメインモデルのユビキタス言語に従うようにする。テストファーストを実践して、常にクラスを使う側の視点で考えるようにする。

● Side-Effect-Free Functions(副作用の無い関数)パターン

様々な副作用*5のあるメソッドを組み合わせてしまうと、どこでどんな影響が発生するか予測できなくなるため、プログラムの振る舞いを理解することが極端に難しくなってしまう。そのため、単に引数を加工して結果を返すだけのロジック部分はできる限り副作用の無い関数としてプログラム化し、実際に状態を変化させる操作(コマンド)を最小限に留める。副作用の無い関数同士ならば、組み合わせて使っても何が起こるか予測できなくなることはない。副作用の無い関数はまた、テストも容易である。値オブジェクトは状態が変わらないので、値オブジェクトがもつ振る舞いには基本的に副作用が無い。1メソッドだけでは表現できない複雑なロジックの場合は、値オブジェクトにそのロジックをもたせることで、そこに副作用が無いこと示すこともできる。

*5 メソッド呼び出しによって、オブジェクトの状態が変化することを「副作用」と言います。オブジェクト指向の計算モデルは、本来このメソッド呼び出し(メッセージ送信)で生じる副作用によって計算を行なうモデルとして定式化されます。

● Assertions(表明)パターン

副作用の無い関数を使えば、ドメインモデル上で副作用の無い部分を分離できるが、それでも副作用のある操作は必ずどこかで必要になる。副作用のある操作を理解しやすい形で表現するには、表明(アサーション)を使って、操作の結果として何が保証されるかという事後条件(post-condition)と、操作の前後で変わらないものは何かという不変項(invariant)とを、明示的に示す。表明の仕組みがサポートされていないプログラミング言語を使っているときは、単体テストを書くことで同様のことが実現できる。ドキュメントや図に表明を記入することも重要である。

(表明の考え方は、第II部で触れたMeyerの「契約による設計」が基礎になっている)

しなやかな設計 ― 適切な分割

「しなやかな設計」のもう1つの要点である、分割についての指針です。ここで重要なことは、適切な粒度の選択と依存関係を極力排除することによって、変更のコストを低減することにあります。

● Conceptual Contours(概念の輪郭)パターン

オブジェクトの粒度に関する話。オブジェクトの粒度は、むやみに細かくしたり粗くしたりすればいい訳ではない。ドメインにはいくつもの概念が隠されていて、それら凝集度の高い概念群の間には目に見えない境界線、つまり概念の輪郭が存在する。モデルのリファクタリングを何度も繰り返すことで概念の輪郭を見つけ出し、オブジェクトの粒度がその輪郭に沿うようにする。これが、ドメインモデリングにおける高凝集のガイドラインである。

(より一般的なオブジェクト指向設計における高凝集の考え方は、第II部で触れたLarmanのGRASPパターンの中で「High Cohesionパターン」としてまとめられている)

● Standalone Classes(独立したクラス群)パターン

人間の頭が一度に考えられる概念の量には、限界がある。ドメインモデルを分かり易くするには、一度に考えなければならない概念の量を絞って、メンタルな負荷の少ないモデルにする必要がある。モデルを構成するクラスのまとまりから、関係のないクラスへの依存関係を極力排除していき、その中だけで独立して理解できる小さな世界を構築しなければならない。これが、ドメインモデリングにおける低結合のガイドラインである。

(より一般的なオブジェクト指向設計における低結合の考え方は、第II部で触れたLarmanのGRASPパターンの中で「Low Couplingパターン」としてまとめられている)

● Closures of Operations(閉じた操作)パターン

ほとんどのオブジェクトの操作は、引数や戻り値の型がプリミティブ型には収まらないことが多い。操作の引数や戻り値に新しいクラスが登場すると、それは新たな依存関係の導入になる。そうした問題を回避するために、操作の結果が、その引数と同じ型のオブジェクトを返すような操作(閉じた操作)の導入を検討する。ここで言う「閉じている」(closed)とは、数学の「自然数は足し算に対して閉じている」、つまりどんな自然数同士を足し合わせても結果はまた自然数になる、という意味と同じ。閉じた操作は、引数と戻り値が同じ型になるので、そこに余計な概念(クラス)を呼び込まなくてすむ。アイデンティティをもつオブジェクトに対して閉じた操作を適用するのは難しいため、ほとんどの場合、閉じた操作は値オブジェクトに対する操作となる。

(似ているけれども異なる概念に、FowlerとEvansが提唱している「流暢なインタフェース」(fluent interface)というものがある。こちらは、メソッド呼び出しの戻り値にさらにメソッド呼び出しをつなげて、文章が流れるようにプログラムを書けるインタフェースの設計テクニックである)

[補足] DDDとデザインパターン

前回はアナリシスパターンとの関連について触れましたが、今度はデザインパターンとの関連について考えてみます。「デザインパターンは純粋に技術的なオブジェクト設計に関するパターンなので、DDDとは関係がないのではないか」というのは、確かにその通りです。アナリシスパターンとは違い、デザインパターンはドメインに関するノウハウを提供するものではありません。

しかし「見方を変える」ことによって、DDDパターンとして利用できるデザインパターンがいくつかあります。なぜなら、概念的な領域の中にも、技術的なオブジェクト設計に見られる構造と同じような構造が発見できるからです。デザインパターンを「概念の構造を示したもの」と読み換えることで、DDDの文脈で使えるようになる訳です。

たとえばStrategyパターンやCompositeパターンは、DDDパターンとして使えるものです。Strategyパターンは、本来は「アルゴリズム」の差し替えを容易にするための設計方法ですが、ドメインモデリングにおいては「プロセス」や「ポリシー」といった概念を表現するのに利用できます。Compositeパターンは、オブジェクトを入れ子構造に設計するためのものですが、ドメインモデリングでは入れ子になった概念を表すために使うことができます。一方でFlyweightパターンのように、純粋に実装上の選択肢を示すだけで、DDDには全く適用できないものもあります。

著者自身も認めているように、どのデザインパターンがドメインにも適用できるのかは、まだ本格的に調べ尽くされた訳ではありません。どのデザインパターンがDDDで使えるのか、読者の皆さんがぜひ開拓していってみて下さい。デザインパターンがDDDパターンとして使えるかを見分ける唯一の判断基準は、パターンが「技術的な問題に技術的な解決法を提供するだけでなく、概念ドメインに対しても何かを示唆しているか」という点だけです。

次回

ここまでで、DDDの前半部分が終了しました。残すは第IV部「Strategic Design」だけですが、まだパターンカタログ全体の半分以上が第IV部には控えています。第III部までは、主に1つのドメインを対象とする1アプリケーションレベルのスケールでの話でした。第IV部では、複数ドメインを扱ったり、他システムとの連携が必要となったりする広大なシステム開発において、いかにしてDDDを実現するかが模索されることになります。

最終回となる次回は、エンタープライズアプリケーション統合(EAI)からSOA、エンタープライズアーキテクチャにまで通ずる設計思想を紹介します。

top ページのトップへ戻る

© 2007 OGIS-RI Co., Ltd.
Prev Index Next
Prev. Index Next