⚙️
概念 #DDD #ドメインサービス #ドメインモデル 📚 実践ドメイン駆動設計

ドメインサービス(Domain Service)

エンティティや値オブジェクトに自然に属さないドメインロジックの置き場所

解決する課題

ドメインのロジックが複数のエンティティ・値オブジェクトにまたがるとき、どこに書けばよいか?どちらか一方に書くと責務過多になり、どちらでもない「ユーティリティクラス」を作るとドメインロジックがサービス層に漏れる。

概念

ドメインサービスは、特定のエンティティや値オブジェクトに自然に属さないドメインロジックをカプセル化するオブジェクト。

以下の条件がそろったときにドメインサービスを導入する:

  1. 操作が本質的にドメインの概念である(インフラや UI の話ではない)
  2. 複数のドメインオブジェクトが関わる
  3. どのエンティティ/値オブジェクトにも属させると不自然

典型的な用途

  • 2つのアカウント間の資金移動(どちらのアカウントに置くか不自然)
  • ユニーク性のチェック(リポジトリを参照する必要があるため、エンティティに持たせられない)
  • 複数集約にまたがる計算・集計

# ❌ どちらのエンティティに責務を持たせるか不自然
class Account:
    def transfer_to(self, target: 'Account', amount: Money) -> None:
        # 自分が操作されながら相手も操作する — 不自然
        ...

# ✅ ドメインサービスで表現
class TransferService:
    def __init__(self, account_repo: AccountRepository):
        self._repo = account_repo

    def transfer(
        self,
        source_id: AccountId,
        target_id: AccountId,
        amount: Money,
    ) -> None:
        source = self._repo.find_by_id(source_id)
        target = self._repo.find_by_id(target_id)
        source.debit(amount)
        target.credit(amount)
        self._repo.save(source)
        self._repo.save(target)

アプリケーションサービスとの違い

ドメインサービスアプリケーションサービス
含む知識ドメインルール・業務ロジックユースケースの調整・オーケストレーション
インフラ依存最小限(Repositoryインターフェースは使える)使う(トランザクション・メッセージング)
ユビキタス言語使う使う(ただし薄い)
テストドメインモデルのみでテスト可能統合テストになりやすい

注意点

  • ドメインサービスを多用しすぎない — 多くのロジックはエンティティ・値オブジェクトに属せるはず。ドメインサービスが増えすぎると「貧血ドメインモデル」の兆候
  • ステートレスに保つ(状態を持たない)

関連概念

出典: 実践ドメイン駆動設計(Vaughn Vernon)第7章