読者です 読者をやめる 読者になる 読者になる

「Application Architecture for .NET」を読んだ - 3部(前編)

.NETのエンタープライズアプリケーションアーキテクチャ 第2版 (マイクロソフト公式解説書)

.NETのエンタープライズアプリケーションアーキテクチャ 第2版 (マイクロソフト公式解説書)

「Application Architecture for .NET」を読んだ - 1部
「Application Architecture for .NET」を読んだ - 2部
の続き

第三部サポートアーキテクチャ(8, 9章)のメモ。

TL;DL

  • ドメインモデル
    • エンティティはビジネス空間に関連するオブジェクトであり同一性とライフタイムを持つ
    • 値オブジェクトは空間内の無生物とも言える存在
    • 一部のエンティティはコーディグや設計上の目的で集約に纏められる
  • モデリングの際の一番の関心毎ではないものの、DB構造が制約である

3. サポートアーキテクチャ

8. ドメインモデルの紹介

データから振る舞いへの移行
  • DDDはレコードの代わりにオブジェクトを使ってデータアクセスコードを記述するだけのアプローチではない
  • ドメインモデルはビジネスのAPI
    • クラスを基本的なビジネスコンセプトと一致させ、常に整合性を保つ事 => データより振る舞いに重点を置く
  • DDDはドメインモデルの作成を優先し、モデリングされたドメインの永続化は後回しにする
ドメイン層の内側
  • アプリケーション層はドメインとインフラ層を操作するためのオーケストレーションコード
  • ドメインモデル: エンティティ/値オブジェクトから構成される
    • 値オブジェクト: データの集まり, instance化されたら変化しない(Immutable), 属性
    • エンティティ: データと振る舞いを持つ, 一意性
      • 振る舞いはドメインロジックとアプリケーションロジックを区別する
        • ユースケースの実装はアプリケーション層に含まれる
        • ex. OrderEntityは税金の計算を行うが、注文の処理はドメインロジックの外側にある => エンティティに関連付けられたユースケースはサービスで実装する
  • モジュール: ドメインモデルを纏める名前空間
  • リポジトリ: エンティティの永続化
    • 契約(コンストラクト)はドメイン層、実装はインフラ層
集約
  • モデル内のエンティティを纏めたり区切ったりする整合性の境界
  • ドメインモデルでは、複数のモデルを1つのコンテナに纏めたものを集約と呼ぶ

    「集約とは、データを変更する目的で1つの単位として扱われる、関連するオブジェクトの集まりです」

  • 値オブジェクトはエンティティから参照される為、集約の一部。複数のエンティティから参照可能で複数の集約で使用可能

  • エンティティが参照できるのは、同じ集約内のエンティティか、別の集約のルートだけ
  • 集約のルートの責務
    • カプセル化されている全てのオブジェクトの永続化に対する責任
    • クエリ操作で取得できるのは集約のルートだけ、内部オブジェクトへのアクセスは集約のルートのインターフェースを通じて発生しなければならない
// 集約ルートを示すmarker
// 集約ルートだけがリポジトリを持つ、リポジトリの型制約として使う
public interface IAggregateRoot {
    bool CanBeSaved { get; }
}

public class Order: IAggregateRoot {
    bool IAggregateRoot.CanBeSaved {
        get { return validate(); }
    }
    // ...
}
ドメインサービス
  • ドメインロジック(特定の集約に属さない、ほとんどの場合複数のエンティティにまたがっている)を実装するメソッドを持つクラス
    • 特定のビジネスロジックが既存の集約のどれとも適合しなかった場合の最後の砦
    • ドメインサービスのインターフェースはユビキタス言語で書かれた契約(コントラクト)を表し、アプリケーションサービスから呼び出せるアクションを定義する
    • 集約をまたいだ振る舞いに用いる
  • リポジトリ
    • 永続化の為に存在する
    • CustomerRepositoryのように集約ルート毎にリポジトリを1つ使用することになる
public interface IRepository<T> where T: IAggregateRoot {
    // interfaceを単なるmarker interfaceにしても良いし、一般的なメソッドを幾つか定義することも出来る
    T Find(object id);
    void Save(T item);
}
  • ドメインイベント
    • ~の「とき」に着目 - ドメイン内に関連するイベントがあるはず
      • 特定の事実が発生したタイミングを知ることにビジネス上の関心があることを示す
public interface IDomainEvent {}
public class CustomerReachedGoldMemberStatus: IDomainEvent {
    public Customer customer { get; set; }
}
  • 横断的関心事
    • 階層化アーキテクチャでは具体的なテクノジに関連するものはInfrastructure層に含まれる
    • CRUD操作は主にリポジトリを通じてコーディグされ、ドメインサービスとその上にあるアプリケーション層を通じて結合される
    • ドメインモデルのクラスではチェックできる物は何でもチェックするようにし、問題が検出された場合は特別な例外をthrowすべき
    • => 例外のエラーメッセージを上位レイヤーに渡し、アプリケーション層ではドメイン例外をそのままプレゼンテーション層に渡すか、飲み込んでしまうか判断する
      • 実際のエラーメッセージはプレゼンテーション層の責務となる

9. ドメインモデルの実装

実用的なドメインモデルを作成するための護身術
  • ドメインモデル貧血症
    • エンティティが貧血と判断されるのは、エンティティに属しているものの¥、エンティティクラスの外側に配置されているロジックがある場合
      • ex. メソッドがエンティティのメンバーのみを扱う場合は、そのエンティティに属している DateTime EstimatedPayment(Invoice invoice)
  • エンティティのscaffolding
    • DDDのエンティティの特徴
      • 同一性が明確に定義されている
        • アプリケーションのライフタイムに渡ってエンティティを一意に識別する目的
      • 振る舞いがpubli/非publicメソッドによって表現される
      • 状態が読み取り専用のプロパティにより表現される
      • プリミティブ型の使用は限定的で値オブジェクトと置き換えられる
      • 複数コンストラクタよりもファクトリメソッドが優先される
  • 値オブジェクトのscaffolding
    • エンティティ同様にデータを集約するクラス(振る舞いとは無関係)
    • 値オブジェクトは可変の状態を持たず、データによって完全に識別されるため、同一性を必要としない
    • メソッドを定義することは可能だが、それらは状態を変更しない純粋なヘルパーメソッド
    • 値オブジェクトの利点は表現性
      • プリミティブ型だと曖昧すぎる
      • データクランプ(新しいオブジェクトに簡単に変換出来るプリミティブ値の集まり)
  • 集約の特定
  • モデルの永続化