「ドメイン駆動設計入門」を読んで_その5

## この記事は

「ドメイン駆動設計入門」を読んでまとめている第5弾

前回記事は[こちら](https://qiita.com/t13801206/items/97dd8462717213524655)

今回は[アプリケーションの組み立て](#アプリケーションを組み立てる)と[集約](#集約)についてまとめる

## アプリケーションを組み立てる

ドメインオブジェクト、サービス、リポジトリ、ファクトリなど、アプリケーションを組み立てるために必要な部品の説明が終わった

それらの部品を使ってアプリケーションを組み立てる手順は以下のとおり

1. 要求にしたがって必要な**機能を洗い出す**
1. 機能を成り立たせるために必要な**ユースケースを洗い出す**
   1. 機能を実現するためには単一のユースケースではなく、ユースケースの組み合わせが必要なときもある
1. アプリケーションにとって必要な知識・ルールを選び、**ドメインオブジェクトをつくる**
1. ユースケースを実現するために、ドメインオブジェクトを用いて**アプリケーションサービスをつくる**

### サークル機能を実現する

手順 2 に書いたように、ある機能を実現するためのユースケースが１つとは限らない
「サークル機能」を実現するにはいくつかのユースケースが考えられる

```plantuml
actor 大学生 as User

User --> (サークルをつくる)
User --> (サークルに入る)
User --> (サークルをつぶす)
User --> (サークルを辞める)
```

さらにサークルには、人数の上限やすでに存在するサークルと同じ名前が使えない、など制約・ルールが存在する

### もう少し詳細な手順

機能はトップダウンで洗い出し、実装はボトムアップで組み立てていく

[参考になる階層分け](https://github.com/nrslib/itddd/tree/master/Layered_UsingInternal/SnsApplication)

1. ルールを持ったドメインオブジェクト（**エンティティ**、**値オブジェクト**）をつくる
   2. ルールが肥大化する場合は仕様という考え方をつかう（次記事）
2. 永続化を担う**リポジトリ**をつくる
3. オブジェクト生成を担う**ファクトリ**をつくる
4. ドメインオブジェクトが持つと不自然になるふるまい（例：重複確認）を**ドメインサービス**に切り出す
5. ユースケースを組み立てる
   1. **コマンド**クラスをつくる
6. **アプリケーションサービス**をつくる

```plantuml
package アプリケーション
{
    class コマンド
    class アプリケーションサービス
    アプリケーションサービス ..-> コマンド
}
アプリケーションサービス --> ドメイン
package ドメイン
{
    package モデル
    {
        interface ファクトリ
        interface リポジトリ
        class エンティティ
        class 値オブジェクト
    }
    package サービス
    {
        class ドメインサービス
    }
}
package インフラ_EF
{
    class EFリポジトリ
}
note bottom: Entity Framework
package インフラ_インメモリ
{
    class インメモリリポジトリ
}
リポジトリ <|.. インメモリリポジトリ
リポジトリ <|.. EFリポジトリ
```

### ルールが漏れ出すと・・・

知識やルールがドメインオブジェクトから漏れ出してプロダクトに点在すると
ルール改正のたびに修正漏れのリスクが生じる

対策として[集約](#集約)という考え方がある

## 集約

> データを変更するための単位としてあつかわれるオブジェクトの集まりを集約といいます(P267)

集約には境界とルートが存在する。外部から集約に対する変更はすべてルートを通しておこなわれる。集約の外部から境界内部のオブジェクトを操作してはいけない。

`UserName`を操作できるのは、ユーザ集約のルートとなる`User`のみ

```cs
user.Name = "Taro"      // Bad !! 直接操作してはいけない
user.ChangeName("Taro") // Good!! メソッドをつかえば、NULL検証などルールと照らし合わせてから変更を受け入れることができる

circle.Members.Add(user) // Bad !! Cicle内部のメンバーリストは公開しないほうがいい
circle.Join(user)        // Good !! こちらの方が文脈的にも自然
```

```plantuml
package ユーザ集約
{
    class User
    note left: ルート
    class UserName
    class UserId
}
User --> UserName
User --> UserId

package サークル集約
{
    class Circle
    {
        Members<User>
    }
    note left: ルート
    class CircleName
    class CircleId
}
Circle --> CircleName
Circle --> CircleId

User <-- Circle : 0..n
```

`Circle`の`Member`リストは外部に公開せず、`private`にしておくとよい

### デメテルの法則

集約をチェックするための１ツールがデメテルの法則

> 簡潔に言うと「直接の友達とだけ話すこと」と要約できる (Wikipedia)

`Circle`は`User`のことだけ知っている

直接ユーザーの名前プロパティにアクセスするのではなく、「名前を変更してね」とだけお願いするとよい

```cs
// 悪い例）ユーザー側でルールを記述している
if(circle.Members.Count > N)
{
    // ...
}

// よい例）ルールをサークルクラスに問い合わせている
if(circle.isFull())
{
    // ...
}
```

### 集約をどう区切るか

`User`と`Circle`は変更の単位で区切られている
裏を返せば`Circle`は`User`を変更してはいけない

もし変更して面倒をみようものなら、サークルリポジトリにユーザ情報変更のロジックが追加（汚染）されてしまう。
当たり前であるが、すでにユーザリポジトリにはユーザ情報変更のロジックがあるため、**同じロジックが重複して点在してしまう**

ロジックの重複・点在は、後々の修正時に修正漏れを引き起こす・・・

#### IDによるコンポジション

しかしながら、`Circle`はメンバーリストとして`User`インスタンスを保持しているので、`user.ChangeName("Taro")`を呼び出せてしまう

どうすればいいか？

`Circle`や`User`はエンティティであるため、IDで識別できる
つまりインスタンス全体を知らなくとも、IDだけでインスタンスを識別できる
IDだけであれば、ほかのメソッドは呼び出されずに済む

```cs
class Circle
{
    public List<User> Members {get; set;}
    // ユーザクラスをつかうと、余計なメソッドを呼ばれる心配がある
    
    // ↓↓↓ 変更 ↓↓↓
    
    public List<UserId> Membsers {get; set;}
    // ユーザIDだけつかうことで、情報を隠ぺいできる。むやみにメソッドが呼ばれない
    // メモリや再構築の手間も省ける
}
```

```plantuml
package ユーザ集約
{
    class User
    {
        + UserId
    }
    note left: ルート\n\nユーザIDを公開する
    class UserName
    class UserId
}
User --> UserName
User --> UserId

package サークル集約
{
    class Circle
    {
       - members<UserId>
    }
    note left: ルート\n\nサークルメンバーは非公開にする\nかつ保持するのはユーザIDだけ
    class CircleName
    class CircleId
}
Circle --> CircleName
Circle --> CircleId

User <-- Circle : 0..n
```

#### 集約の大きさ

集約が変更されるたびにトランザクションがおきる
集約が大きくなると、トランザクションが失敗する可能性も大きくなる
トランザクションが失敗し、リトライ頻度が大きくなるとアプリケーションの質が低下する

大きくなりすぎた集約は境界を見直すべき