From ddd-skills
ドメインサービスの設計と、ロジックをエンティティ・Value Object・ドメインサービスのどこに置くかの判断を支援する。 次のような依頼があったときに使う:「ドメインサービスを設計する」「domain serviceを作る」 「このロジックをエンティティに置けないか検討する」「複数の集約にまたがるロジックをどうするか」 「サービスクラスを整理する」「ドメインサービスをレビューする」「重複チェックをどこに置くか」。 または、ドメインサービス、domain service、複数集約にまたがるルール、ドメインポリシーに言及する場合にも使う。
How this skill is triggered — by the user, by Claude, or both
Slash command
/ddd-skills:domain-service [ロジックやドメインの説明][ロジックやドメインの説明]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
DDD におけるドメインサービスの設計と、ドメイン層内でのロジック配置(エンティティ / Value Object / ドメインサービス)の判断を支援する。
DDD におけるドメインサービスの設計と、ドメイン層内でのロジック配置(エンティティ / Value Object / ドメインサービス)の判断を支援する。
注: ドメイン層かアプリケーション層かの判定は domain-classifier スキルが担う。本スキルは「ドメイン層に属する」と判定されたロジックの、ドメイン層内での置き場所を扱う。
特定のエンティティや Value Object に自然に属さないドメインロジックを表現する、ステートレスなオブジェクトである。
特性:
TransferService、PricingPolicy)ドメインサービスは安易に作らない。まずエンティティ・Value Object に置けないかを必ず検討する。
このドメインロジックをどこに置くか?
│
├─ 1つのエンティティの状態とルールで完結する
│ └→ そのエンティティのメソッド
│
├─ 値の検証・計算・変換である
│ └→ Value Object のメソッド
│
├─ 1つの集約内で完結する
│ └→ 集約ルートのメソッド
│
├─ 複数の集約・エンティティにまたがり、
│ どれか1つに置くと不自然になる
│ └→ ドメインサービス
│
└─ ドメイン全体に適用される方針・ルールである
└→ ドメインサービス(ポリシー)
リトマステスト: 「この操作の主語は誰か?」主語が特定のエンティティなら、そのエンティティのメソッドである。主語が見つからない(操作そのものが概念である)ならドメインサービスの候補である。
混同しやすいが責務が異なる。
| 観点 | ドメインサービス | アプリケーションサービス |
|---|---|---|
| 内容 | ビジネスルールそのもの | ユースケースの手順制御 |
| 状態 | ステートレス | ステートレス |
| 依存 | ドメインオブジェクトのみ(+リポジトリインターフェース) | リポジトリ、外部サービス、トランザクション |
| 例 | 送金額の妥当性判定、与信評価 | 「注文確定→在庫引当→通知」の調整 |
| ドメインエキスパートに通じるか | 通じる | 技術的な手順を含む |
判断に迷う場合は domain-classifier スキルの判定ツリーを使う。
TransferService:
transfer(from: Account, to: Account, amount: Money) -> TransferResult:
assert from.canWithdraw(amount), "残高が不足している"
from.withdraw(amount)
to.deposit(amount)
return TransferResult.success(from.id, to.id, amount)
送金は「どちらの口座のメソッドか」を決められない。操作そのものが概念である。
DiscountPolicy:
applicableDiscount(customer: Customer, order: Order) -> Discount
割引方針は顧客にも注文にも属さない、独立したビジネスルールである。実装が複数ある場合はインターフェースとして定義し、差し替え可能にする。
一意性チェックなど、コレクション全体への問い合わせが必要なルール。
CustomerUniquenessChecker:
// リポジトリのインターフェース(ポート)にのみ依存する
isDuplicate(email: EmailAddress) -> boolean:
return customerRepository.existsByEmail(email)
エンティティは自分以外のインスタンスを知らないため、エンティティには置けない。
業務的な採番ルール(年度プレフィックス付き連番等)は採番サービスとして明示する。
〜Service を機械的に付けず、PricingPolicy、UniquenessChecker のように責務を表す名前を検討する| アンチパターン | 問題 | 対処 |
|---|---|---|
| ロジックの吸い上げ | エンティティに置けるロジックをサービスに集め、貧血モデル化する | 「最後の手段の原則」で配置を見直す |
| Manager / Helper / Util 命名 | 責務が不明確で何でも入る | ドメインの概念で命名し直し、責務を分割する |
| 状態を持つサービス | ステートレス原則違反。並行性の問題を生む | 状態はエンティティか引数に移す |
| 手順の混入 | トランザクション制御・通知などユースケースの手順を含む | アプリケーション層に移動する(domain-classifier 参照) |
| インフラへの直接依存 | 外部API・ORM をドメインサービスから呼ぶ | インターフェース(ポート)を定義しインフラ層に実装を置く |
| 巨大サービス | 1つのサービスに無関係なルールが同居する | 概念ごとに分割する |
サービスクラスの肥大化を見直す際は、以下の手順で進める:
examples/ ディレクトリに具体例がある:
examples/domain-service-example.pseudo — 送金・ポリシー・一意性チェックの実装例npx claudepluginhub dskst/ddd-skills --plugin ddd-skillsCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.