Open8

ドメイン駆動設計入門を読んでの整理

ピン留めされたアイテム
Shohei KakimotoShohei Kakimoto

ドメイン駆動設計概要

  • 知識を表現するパターン
    • 値オブジェクト
    • エンティティ
    • ドメインサービス
  • アプリケーションを実現するパターン
    • リポジトリ
    • アプリケーションサービス
    • ファクトリ
  • 知識を表現する、より発展的なパターン
    • 集約
    • 仕様

知識を表現するパターン

  • ドメイン(業務知識)をコード化したい
  • そのためにドメインオブジェクトと呼ばれるオブジェクトを使って表現するためのグループ

アプリケーションを実現するパターン

  • ドメインオブジェクトを用意するだけではドメインをコード化しただけ
  • ドメインオブジェクトを使用してアプリケーションとして利用者の要求を満たすためのグループ

知識を表現する、より発展的なパターン

  • より発展的に知識を表現するためのグループ
Shohei KakimotoShohei Kakimoto

バリューオブジェクトとは

  • 不変
  • 交換が可能
  • 等価性によって評価される

不変

一度作成されたオブジェクトは中身が変更されてはいけないというルール
変更を許可してしまうと、別名参照問題を引き起こしてしまう可能性がある

交換が可能

新しく作成される場合は代入するのではなく、新しくインスタンス化することによって再生成する必要がある。

$user = new User("Yamada", "Taro");
$user = new User("Suzuki", "Jiro"); // 新しく作り直す

個人的には「交換が可能」ではなく「交換しなくてはいけない」と解釈しています

等価性によって評価される

値が同じであれば同じものだとみなします。

$user1 = new User("Yamada", "Taro");
$user2 = new User("Yamada", "Taro");

// オブジェクト同士の比較で等価性を評価する
if ($user1== $user2) reutrn true

バリューオブジェクトにする基準

  • 場合による
  • その値についてルールが存在しているか
  • 単体で扱いたいか

オブジェクトの振る舞いについて

バリューオブジェクトには振る舞いを定義することができる
以下はPrcieオブジェクトの例

class Price()
{
 // 税込価格を返す
 public function taxIncludingPrice(float $price): float
 {
  return $price * 1.1;
 }
}

バリューオブジェクトのメリット

  • 表現力が増す
  • 不正な値を存在させない
  • 誤った代入を防ぐ
  • ロジックの散財を防ぐ
Shohei KakimotoShohei Kakimoto

エンティティとは

  • 可変である
  • 同じ属性であっても区別する
  • 同一性によって区別される

可変である

オブジェクトが持っている値は後から変更することができる

class User
{
 private $name;

 // 名前を変更する
 public function changeName(string $name): void
 {
  // 必要に応じてバリデーションなどを入れる

  $this->name = $name;
 }
}

同じ属性であっても区別される

同じ属性(名前や苗字など)を持っていても別のものと区別される
ただし、それぞれを識別するための識別子(IDなど)が必要になる

class User
{
 private $id; // 識別子を使用して区別される
 private $name;

 private function setId(int $id): void
 {
  $this->id = id;
 }

 private function setName(string $name): void
 {
  $this->name = $name;
 }
}

同一性によって区別される

オブジェクトが持つ値が変更されても同じものとして扱われる

class User
{
 private $id;
 private $name;

 private function setId(int $id): void
 {
  $this->id = id;
 }

 private function setName(string $name): void
 {
  $this->name = $name;
 }

  public function changeName(string $name): void
 {
  $this->name = $name; // 名前が変更されても識別子が同一のため同じものと判断される
 }
}

エンティティにする判断

  • ライフサイクルがある場合はエンティティとして取り扱う
    • ライフサイクルのあるデータは内容が変更されることが予想される
  • それ以外は一旦値オブジェクトとして扱う

ドメインオブジェクトを定義するメリット

  • コードのドキュメント性が高まる
  • ドメインの変更をコードに伝えやすくする

コードのドキュメント性が高まる

ロジックがドメインオブジェクト内に集まっているため、他の人がそのコードを読んだときにこコードの意図を汲み取りやすく保守がしやすい

ドメインの変更をコードに伝えやすくする

ドメインオブジェクトにルールや振る舞いが集まっているため、ドメイン(業務知識、業務仕様)が変更になった場合もどの変更をコードに反映しやすい

Shohei KakimotoShohei Kakimoto

ドメインサービスとは

  • ドメインオブジェクトに定義するべきではない処理を記述する
  • 状態を持たないオブジェクト(ステートレス)
class UserDomainService
{
 // Userの重複を確認する
 // ドメインオブジェクトにあるべきではない処理
 public function exists(User $user)
 {
  // 重複を確認するコード
 }
}
Shohei KakimotoShohei Kakimoto

リポジトリとは

  • オブジェクトのデータを永続化・復元を行う
    • そのためにDBとのやり取りをする責務を負う

インターフェースの利用

インターフェースを用意してあげることで、例えばDBがMySQLでもElasticSearchでも切り替えやすくなる(依存性の逆転)

リポジトリに定義される振る舞い

  • データの永続化(保存)
  • データの再構築(取得)

データの永続化(保存)

SaveメソッドのようなDBへのデータの永続化を行う。削除系の処理もリポジトリの責務。
ただし更新系の処理はリポジトリの責務ではない。オブジェクトが保存するデータを変更する場合は、オブジェクト自身がその仕事をするべき。

・保存と削除

class UserRepository implements IUserRepository
{
 public function save(User $user): void
 {
  // 保存処理
 }

 public function delete(User $user): void
 {
  // 削除処理
 }
}

データの再構築(取得)

識別子を利用して検索を行う。

class UserRepository implements IUserRepository
{
 public function getById(int $id): User
 {
  return DB::table('Users')->where('id', $id)->get();
 }
}
Shohei KakimotoShohei Kakimoto

アプリケーションサービスとは

  • アプリケーションの要求を実現するためにユースケースを組み立てる役割
    • 会員登録
    • 会員情報確認
    • 会員情報更新
    • 退会
  • クライアントとしてドメインオブジェクトを操作する
    • エンティティや値オブジェクト、ドメインサービスの振る舞いを呼び出す
Shohei KakimotoShohei Kakimoto

ファクトリとは

  • オブジェクト生成の責務を負うオブジェクト
  • コンストラクタで行っていた処理をファクトリに書き出す
サンプルを書く
Shohei KakimotoShohei Kakimoto

集約とは

  • 不変条件を切り出す単位

集約の基本的構造

集約の外から内部のオブジェクトを操作してはいけない。操作するのは集約ルートに限定される。つまりその集約が保持しているデータの変更はそのオブジェクト自体が行わなくてはいけない。そのように操作することで内部の不変条件を保つことができる。

オブジェクト操作に関する基本的な原則

不変条件を保持するための「デメテルの法則」ではメソッドを呼び出すことができるのは以下の4つ

  • オブジェクト自身
  • インスタンス変数
  • 引数として渡されたオブジェクト
  • 直接インスタンス化したオブジェクト

集約をどう区切るのか

  • 最もメジャーな単位は「変更の単位」

変更の単位

もしある集約(ex Circle集約, User集約)が他の集約を操作する処理を保持していた場合、そのほとんどが他の集約を操作する処理で汚染されてしまう。
集約に対する変更はその集約自信が担当する必要があり、永続化の依頼も集約ごとに行われる必要がある。このような理由によりリポジトリは変更の単位である集約ごとに用意されることが必要である。