FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

エリック・エヴァンスのドメイン駆動設計 読書メモ2

元記事はこちら

読書メモ1 の続き。 引き続き読書しています。第2部を読みました。

第2部 モデル駆動設計の構成要素

第4章 ドメインの隔離

UIやDB、補助的コードなどに侵食されることないようドメインモデルを隔離する。 ドメインモデルを表すコードを理解可能にするため。

レイヤードアーキテクチャドメイン層を隔離する手段の一つとして使える。 UI、アプリケーション、ドメイン、インフラストラクチャをこの順序でレイヤとする(UIが最上位)。 各レイヤで凝集度を高め、下位レイヤにのみ依存する。上位レイヤへのアクセスは callback, observer などのパターンが利用できる。

フレームワーク(J2EEなど)を使う場合にも、ドメインモデルを表現し、重要な問題を解決するという点を主眼に置く。 フレームワークに振り回されることなく、目的を達成する上で必要な部分だけを選択して使うと良い。

Smart UI

UIにビジネスロジックを実装する方法のこと。UIとドメインの分離は行わない。 単純なアプリケーションの場合は生産性が高い。開発者に高い能力を要しない。単純であるかぎり拡張もうまくいく。 複雑さが現れてくると対処できない。

UIとドメインは分離するが、オブジェクトモデルは提供しない Transaction script もある。

アンチパターンとして紹介されているが、適材適所で採用するべきだろう。

第5章 ソフトウェアで表現されたモデル

モデルと実装を対応させるには、実装からモデルへのフィードバックが必要だ。

関連

多対多、双方向の関連が多くあると、実装が複雑になる。 実装を単純に方法として次のものがある。

  • 単方向にする
  • 限定子を付与し、多重度を減らす
  • 本質的ではない関連を除く

ドメインを深く理解すると、関連の多重度を減らし、単方向にできることが多い。 関連に対する制約が見つかったらモデルと実装に含めることで、モデルは正確さが増し、実装は保守性が上がる。

Entity (aka. reference object)

属性が変わっても同一性が保たれるオブジェクトを entity と呼ぶ。 私という人間は体重が変わっても私であり続ける。

Entity には識別手段を用意する必要がある。ID(識別子)を割り当てることで実現できる。 属性による entity 同一性チェックが行われていたら何か間違ってる。

Entityの最も基本的な責務は連続性を確立すること。 そのために本質的なふるまいとそれに必要な属性だけを選択する。ふるまいと属性を関連した他のオブジェクトへ移せないか調べること。 オブジェクトの同一性を判定する際に、手段の一部として使われることが多い属性は entity に含めるべき。 (例: 名前は顧客entityを特定できないが、特定手段の一部として使われることが多い)

Value object

属性にしか関心の対象とならないモデル対象は value object。 Value object は不変にすることで、実装が単純化され、共有や参照渡しが安全になる。

Value object が entity を参照することもある。

値オブジェクト同士の双方向関連は完全に取り除くよう努力すること。 取り除けない場合は、認識されていない同一性が存在する可能性がある。

Service

どのオブジェクトにも属さない操作を Service として定義する。 Service は状態をカプセル化しない。 Entity と value object に含めるべきふるまいを service にしてはならない。

Service はドメインレイヤ以外でも考えることができる。レイヤ違反しないように注意する。

アプリケーションレイヤで細粒度のドメインオブジェクトを使うことでドメイン知識が流出することがあるかもしれない。 その場合には粒度を調整する手段としてドメイン service が使える。

Module (aka. package)

認知負荷を下げるためにモジュール化する。 凝集度の高いモデル要素を単一モジュールにまとめ、結合度の低いモデル要素を別モジュールにする。

モデルが物語だとすれば、モジュールは章に相当する。

モジュールをコミュニケーションの仕組みとして捉え、モジュール名で会話のコンテキストが揃うようにする。

モジュールはリファクタリングが高コストなため、リファクタリング対象になりづらい。 問題が起きそうな箇所は歯を食いしばってリファクタリングする必要がある。

レイヤードアーキテクチャの各レイヤを別モジュールにすることで物理的に分離するのは非常に有効な例。 分散させる(などの技術的な)意図がない限り、一つの概念は同一モジュールにまとめること。

モデリングパラダイム

オブジェクトモデリングは、多くの人に「自然に」理解されやすく、十分な表現度がある。 人気があるため、インフラストラクチャとツールサポートも豊富であり、開発者やコミュニティも容易に見つけられる。

オブジェクトモデリングしているシステムに非オブジェクトモデリング(例: ルールエンジン)を混ぜることもできる。 混ぜる価値があるか慎重に検討し、各パラダイムに適した形で概念をモデリングする。 単一のモデルに従って設計、実装を進めることで、システムの分離を避けることができる。

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

生成されてから削除されるライフサイクルを通じて、ドメインオブジェクトは整合性を維持しなければならない。 またライフサイクル管理の複雑さがモデルを侵食してはならない。 集約、ファクトリ、リポジトリの3パターンでこれを達成する。

集約

関連が複雑に絡み合うのは現実を反映している。 関連が絡み合った状態で、オブジェクトを変更した際に不変条件を保証することは難しい。 むやみにロックを獲得すると相互干渉が激しくなるという課題がある。 DBトランザクションの問題のように見えるが、モデルに境界が定義されていないことが問題。

関連するオブジェクトの集まりを集約と呼ぶ。 含まれる対象を決めるのが集約境界。 集約ルートは集約内にある特定の 1 entity であり、グローバルな同一性をもつ。 集約ルートは不変条件をチェックする責務を負う。 外部オブジェクトから参照を保持してよいのは集約ルートのみ。集約ルート以外への参照は一時的な利用に留める。 集約ルート以外の entity はローカルな同一性をもつ。

DBから直接取得できるのは集約ルートのみになる。 集約境界内の変更をコミットするときには不変条件が満たされていなければならない。

ファクトリ

オブジェクトの生成は主要な操作になりうる。 複雑な生成操作は生成されるオブジェクトの責務に合わない。 クライアントに構築させると、クライアントの設計を混乱させ、カプセル化違反し、クライアントと過度に結合する。

ファクトリは生成に必要な知識をカプセル化する。 生成したオブジェクトは一貫した状態であること。集約全体をまとめて生成する場合は不変条件を満たすこと。 ファクトリはドメインの一部として扱う。

集約ルートにファクトリメソッドをおいても良いし、特定のオブジェクトが生成に関わる多くの情報を持っている場合はそのオブジェクトにファクトリをおいても良い。 ファクトリは生成されるオブジェクトと密接な関係があるオブジェクトに置くべきだが、生成されたオブジェクトが同じ集約に属するとは限らない。

次の場合はファクトリではなくコンストラクタで良い。

  • 実装クラスが戻り値の型である(型が抽象化されていない)とき
  • オブジェクトの生成がネストしないとき
  • 構築が単純なとき

Entity ID の割当はファクトリで制御するのが良い。

リポジトリ

特定の型のオブジェクトを集合の概念で表現したもの。 コレクションと動作が似ているがクエリ機能を持っている。 直接アクセスが必要な集約ルートに対してのみリポジトリを提供する。

開発者はリポジトリ実装を意識して性能問題などが出ないように気を配る必要がある。

リポジトリトランザクション制御をクライアントに任せたほうが単純になる。 永続化フレームワークを採用する場合、フレームワークとフィットする方法を模索すること。 対立したときには、DDDの基本を保ちながら詳細は捨てる。

ファクトリがオブジェクトの生成を扱い、リポジトリは再構成を扱う。 再構成時に不変条件を満たさない場合、すでにDB上にあるデータを扱う戦略が必要。 リポジトリはオブジェクト生成部分をファクトリに委譲することがある。

第7章 言語を使用する: 応用例

貨物輸送システムの設計を通じて手法の適用サンプルを示している。

  1. ドメインの隔離 (アプリケーションレイヤの導入)
  2. entity と value object の識別
  3. 関連の設計、単純化
  4. 集約の設計
  5. レポジトリの選択
  6. ファクトリの実装
  7. モジュール分割
  8. 追加仕様への対応

感想

ひとつのモデルをコミュニケーションでも利用するということは、ドメインエキスパートにも entity, value object, 集約などの実装上のパターンに対する理解を求めることにつながるように思う。 前提知識がない状態で「実装上の都合によりモデルを変えます」と言われたときに納得できるだろうか?

またレイヤードアーキテクチャを採用するには、ドメインエキスパートがレイヤの境界を把握する必要がある。 ドメインのことを他の人より多く知っているという理由でドメインエキスパートが据えられている場合、「ユーザがooを入力したら赤い文字でxxが表示される」などUIレイヤの話をしてしまうことも多いように思う。

開発者以外に求める負荷が高く不安を感じる。