ドメイン駆動設計（DDD）の実践：ライブ配信アプリの例

#### はじめに
[前回の記事](https://qiita.com/ma2mori/items/3b080bf55732c505eef6)では、ドメイン駆動設計（DDD）の基本概念を学びました。今回は「ライブ配信アプリ」を題材に、具体的な実践例を紹介します。

### ライブ配信アプリの概要
ライブ配信アプリは、ユーザーがリアルタイムでコンテンツを配信し、他のユーザーがそれを視聴するプラットフォームです。以下のような特徴があります。
- 視聴ユーザーは配信ユーザーにもなり得る
- 視聴者は無料および有料のリアクションで配信者にフィードバック可能

その為モデリングにおける考慮要素は主に以下になります。
- ユーザー管理
- 配信管理
- リアクション管理

※実際はドメインエキスパートと協力し詳細を詰めていくと思います

### ドメインモデリング
まずは、ライブ配信アプリの主要なエンティティ、値オブジェクト、集約、サービス、リポジトリを定義します。

#### エンティティ
```mermaid
classDiagram
    class User {
        - string userId
        - string name
        - ProfileInfo profileInfo
        - array followers
        + __construct(string userId, string name, ProfileInfo profileInfo, array followers)
    }

    class LiveStream {
        - string streamId
        - User streamer
        - DateTime startTime
        - DateTime endTime
        - array viewers
        + __construct(string streamId, User streamer)
        + endStream() void
        + addViewer(User viewer) void
    }

    User "1" --> "1" ProfileInfo
    LiveStream "1" --> "0..*" User : viewers
    User "0..*" --> "0..*" UserId : followers
```

- **ユーザー**
  - 属性: ユーザーID、名前、プロフィール情報、フォロワーリスト
- **配信**
  - 属性: 配信ID、配信者（ユーザー）、開始時間、終了時間、視聴者リスト

#### 値オブジェクト
```mermaid
classDiagram
    class ProfileInfo {
        - int age
        - string gender
        - string bio
        + __construct(int age, string gender, string bio)
    }

    class Comment {
        - string content
        - User author
        - DateTime postedAt
        + __construct(string content, User author, DateTime postedAt)
    }

    class Reaction {
        - string type
        - string content
        - DateTime time
        + __construct(string type, string content)
    }

    class PaidReaction {
        - float amount
        + __construct(string content, float amount)
    }

    Comment --> User
    PaidReaction --> Reaction
```

- **プロフィール情報**
  - 属性: 年齢、性別、自己紹介など
- **コメント**
  - 属性: コメント内容、投稿者（ユーザー）、投稿時間
- **リアクション**
  - 属性: リアクションタイプ（無料、有料）、リアクション内容、リアクション時間

#### 集約
```mermaid
classDiagram
    class LiveStreamAggregate {
        - LiveStream liveStream
        - array comments
        - array reactions
        + __construct(LiveStream liveStream)
        + addComment(Comment comment) void
        + addReaction(Reaction reaction) void
    }

    LiveStreamAggregate "1" --> "1" LiveStream
    LiveStreamAggregate "1" --> "0..*" Comment
    LiveStreamAggregate "1" --> "0..*" Reaction
```

- **配信集約**
  - 配信を中心に関連エンティティ（コメント、リアクション）をまとめたもの

#### サービス
```mermaid
classDiagram
    class LiveStreamService {
        - LiveStreamRepository repository
        + __construct(LiveStreamRepository repository)
        + startStream(string streamId, User streamer) void
        + endStream(string streamId) void
    }
```

- **配信管理サービス**
  - 配信の開始・終了、視聴者の管理、リアクションの集計

#### リポジトリ
```mermaid
classDiagram
    class UserRepository {
        + save(User user) void
        + findById(string userId) User
    }

    class LiveStreamRepository {
        + save(LiveStream liveStream) void
        + findById(string streamId) LiveStream
    }
```

- **ユーザーリポジトリ**
  - ユーザー情報の取得・保存
- **配信リポジトリ**
  - 配信情報の取得・保存

### ユースケース
ここでは、ライブ配信アプリの簡単なユースケースを通じて、DDDの要素を実践的に見ていきます。以下の例はPHPです。

#### ユースケース: 配信の開始と終了

1. **配信の開始**
   - ユーザーが配信を開始すると、新しい配信エンティティが作成されます。

```php
<?php

class ProfileInfo {
    public function __construct(
        private int $age,
        private string $gender,
        private string $bio
    ) {}

    // getter methods
}

class User {
    private array $followers;

    public function __construct(
        private string $userId,
        private string $name,
        private ProfileInfo $profileInfo,
        array $followers = []
    ) {
        $this->followers = array_map(fn($follower) => new UserId($follower), $followers);
    }

    // getter methods
}

class UserId {
    public function __construct(private string $id) {}

    public function __toString(): string {
        return $this->id;
    }
}

class LiveStream {
    private DateTime $startTime;
    private ?DateTime $endTime;
    private array $viewers;

    public function __construct(
        private string $streamId,
        private User $streamer
    ) {
        $this->startTime = new DateTime();
        $this->endTime = null;
        $this->viewers = [];
    }

    public function endStream(): void {
        $this->endTime = new DateTime();
    }

    public function addViewer(User $viewer): void {
        $this->viewers[] = $viewer;
    }

    // その他のメソッド...
}
```

2. **配信の終了**
   - 配信が終了すると、配信エンティティの終了時間が設定され、必要なデータが保存されます。

```php
<?php

class LiveStreamRepository {
    private array $liveStreams = [];

    public function save(LiveStream $liveStream): void {
        $this->liveStreams[$liveStream->streamId] = $liveStream;
    }

    public function findById(string $streamId): ?LiveStream {
        return $this->liveStreams[$streamId] ?? null;
    }
}

class LiveStreamService {
    public function __construct(private LiveStreamRepository $repository) {}

    public function startStream(string $streamId, User $streamer): void {
        $liveStream = new LiveStream($streamId, $streamer);
        $this->repository->save($liveStream);
    }

    public function endStream(string $streamId): void {
        $liveStream = $this->repository->findById($streamId);
        if ($liveStream) {
            $liveStream->endStream();
            $this->repository->save($liveStream);
        }
    }
}
```

#### ユースケース: リアクションの送信

1. **無料リアクションの送信**
   - ユーザーが無料のリアクションを送信するユースケースです。

```php
<?php

class Reaction {
    private DateTime $time;

    public function __construct(
        private string $type,
        private string $content
    ) {
        $this->time = new DateTime();
    }

    // getter methods
}

class LiveStream {
    private array $reactions = [];

    public function addReaction(Reaction $reaction): void {
        $this->reactions[] = $reaction;
    }

    // その他のメソッド...
}
```

2. **有料リアクションの送信**
   - ユーザーが有料のリアクションを送信するユースケースです。

```php
<?php

class PaidReaction extends Reaction {
    public function __construct(
        string $content,
        private float $amount
    ) {
        parent::__construct('paid', $content);
    }

    // getter methods
}
```

#### ドメインモデル図
```mermaid
classDiagram
    class User {
        - string userId
        - string name
        - ProfileInfo profileInfo
        - array followers
        + __construct(string userId, string name, ProfileInfo profileInfo, array followers)
    }

    class ProfileInfo {
        - int age
        - string gender
        - string bio
        + __construct(int age, string gender, string bio

)
    }

    class UserId {
        - string id
        + __construct(string id)
        + __toString() string
    }

    class LiveStream {
        - string streamId
        - User streamer
        - DateTime startTime
        - DateTime endTime
        - array viewers
        + __construct(string streamId, User streamer)
        + endStream() void
        + addViewer(User viewer) void
    }

    class Reaction {
        - string type
        - string content
        - DateTime time
        + __construct(string type, string content)
    }

    class PaidReaction {
        - float amount
        + __construct(string content, float amount)
    }

    LiveStream "1" --> "0..*" Reaction
    LiveStream "1" --> "0..*" User : viewers
    User "1" --> "1" ProfileInfo
    User "0..*" --> "0..*" UserId : followers
    PaidReaction --> Reaction
```

### DDDアプローチのメリット
今回のアプローチによるメリットは以下の通りです。

- **明確なビジネスロジックの分離**
  - ビジネスロジックをドメインモデルに集中させることで、コードの可読性と保守性が向上します。
- **高度な抽象化**
  - 複雑なドメインを抽象化し、エンティティ、値オブジェクト、サービス、リポジトリといった概念に分割することで、設計がシンプルかつ強固になります。
- **一貫性の確保**
  - 集約の境界内でデータの整合性を保証することで、システム全体の一貫性を保ちます。
- **テスト容易性**
  - ドメインモデルが明確に定義されているため、ユニットテストや統合テストが容易に行えます。
- **拡張性の向上**
  - 変化するビジネス要件に対して、モデルの拡張や修正が容易です。

### まとめ
この記事では、ライブ配信アプリを例にしてドメイン駆動設計（DDD）の基本概念と実践方法を紹介しました。DDDのアプローチを用いることで、複雑なビジネスロジックを効果的に管理できるようになります。より価値あるソフトウェアを作るために引き続き学んでいきたいと思います。