⚙️
ドメインサービス(Domain Service)
エンティティや値オブジェクトに自然に属さないドメインロジックの置き場所
解決する課題
ドメインのロジックが複数のエンティティ・値オブジェクトにまたがるとき、どこに書けばよいか?どちらか一方に書くと責務過多になり、どちらでもない「ユーティリティクラス」を作るとドメインロジックがサービス層に漏れる。
概念
ドメインサービスは、特定のエンティティや値オブジェクトに自然に属さないドメインロジックをカプセル化するオブジェクト。
以下の条件がそろったときにドメインサービスを導入する:
- 操作が本質的にドメインの概念である(インフラや UI の話ではない)
- 複数のドメインオブジェクトが関わる
- どのエンティティ/値オブジェクトにも属させると不自然
典型的な用途
- 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インターフェースは使える) | 使う(トランザクション・メッセージング) |
| ユビキタス言語 | 使う | 使う(ただし薄い) |
| テスト | ドメインモデルのみでテスト可能 | 統合テストになりやすい |
注意点
- ドメインサービスを多用しすぎない — 多くのロジックはエンティティ・値オブジェクトに属せるはず。ドメインサービスが増えすぎると「貧血ドメインモデル」の兆候
- ステートレスに保つ(状態を持たない)
関連概念
- → エンティティ
- → アプリケーションサービス
- → 集約
- 1. 🗺️サブドメイン(コア・サポート・汎用)
- 2. 🗾コンテキストマップ
- 3. ⬡六角形アーキテクチャ(ポートとアダプター)
- 4. 🪪エンティティ(Entity)
- 5. ⚙️ドメインサービス(Domain Service)
- 6. 📣ドメインイベント(Domain Event)
- 7. 🫧集約(Aggregate)
- 8. 📐集約の設計原則(Vernon の4ルール)
- 9. 🗄️リポジトリ(Repository)
- 10. 🏭ファクトリ(Factory)
- 11. 📦モジュール(Module)
- 12. 🎛️アプリケーションサービス(Application Service)
- 13. 🔌境界づけられたコンテキストの統合
- 14. ⚡CQRS(コマンドクエリ責任分離)
- 15. 📜イベントソーシング(Event Sourcing)
出典: 実践ドメイン駆動設計(Vaughn Vernon)第7章