ドメインサービス
読了時間: 約12分
はじめに
ドメインサービスはPrism Architectureの重要なコンポーネントであり、単一のエンティティに自然に属さないビジネスロジックをカプセル化します。ドメインサービスは、複数のエンティティに対して操作を行ったり、特殊なロジックを必要とするビジネスルール、バリデーション、プロセスの実装において重要な役割を果たします。この文書では、ドメインサービスとは何か、Prism Architectureにおいてどのように適合するか、そしてシステム内の他のタイプのサービスとどのように異なるかを探ります。
ドメインサービスを理解することは、ビジネス概念とルールを正確に表現する豊かで構造化されたドメインモデルを実装するために不可欠です。適切なドメインサービスにビジネスロジックを配置することで、ビジネスドメインの意図を明確に伝える、より保守性が高く、テスト可能で、表現力のあるアーキテクチャを作成できます。
ドメインサービスとは何か?
定義と目的
ドメインサービスは、特定のエンティティや値オブジェクト内に自然に適合しないドメインロジックを実装するオブジェクトです。ドメインサービスはドメイン内の重要なプロセスや変換を表し、Prism Architectureのドメイン層の一部です。
ドメインサービスの主な目的には以下が含まれます:
- ビジネスプロセスの実装: 複数のエンティティを含むドメイン操作を表現
- ドメインルールの適用: エンティティ境界にまたがる複雑なビジネスルールの実装
- エンティティ間の相互作用の調整: 異なるドメインオブジェクト間の相互作用を管理
- ドメイン抽象化の提供: ドメイン概念の高レベル抽象化を作成
エンティティとは異なり、ドメインサービスは主に「何であるか」ではなく「何をするか」によって定義されます。ドメインサービスはドメインモデル内の「もの」ではなく、振る舞いや操作を表します。
主な特徴
ドメインサービスの特徴:
- ステートレス性: 通常、操作間で状態を維持しない
- ドメイン重視: ドメインの概念と用語を表現する
- 純粋なビジネスロジック: ビジネスロジックのみを含み、インフラストラクチャやアプリケーションの懸念事項は含まない
- ドメインアクティビティに基づいた命名: ドメインプロセスを反映した名前を使用(例:
PaymentProcessor
、TaxCalculator
) - ドメインオブジェクトパラメータ: ドメインエンティティと値オブジェクトを操作する
- 明示的なインターフェイス: ドメイン操作を表現する明確で意味のあるインターフェイスを定義
ドメインサービスの種類
ドメインサービスは、その主な責任に基づいていくつかのタイプに分類できます。これらのタイプを理解することで、ドメインに適切なサービスを特定し設計するのに役立ちます。
プロセスサービス
プロセスサービスはドメイン内の重要なビジネスプロセスを実装します:
- 目的: 重要なビジネスワークフローや変換を表現
- 特徴: 多くの場合、複数のエンティティまたはステップを含む
- サービス例:
OrderProcessor
、PaymentService
、ReservationManager
- 操作例:
processOrder()
、makePayment()
、createReservation()
計算サービス
計算サービスは複雑なビジネス計算を実装します:
- 目的: 単一のエンティティに属さないドメイン固有の計算を実行
- 特徴: 多くの場合、ビジネスアルゴリズムや公式を実装
- サービス例:
PricingCalculator
、TaxService
、InterestCalculator
- 操作例:
calculatePrice()
、determineTaxes()
、computeInterest()
バリデーションサービス
バリデーションサービスは複雑なドメインバリデーションロジックを実装します:
- 目的: 複数のエンティティにまたがるビジネスルールを適用
- 特徴: 通常、例外をスローするのではなくバリデーション結果を返す
- サービス例:
OrderValidator
、CreditApplicationEvaluator
、ComplianceChecker
- 操作例:
validateOrder()
、evaluateCreditApplication()
、checkCompliance()
コーディネーションサービス
コーディネーションサービスは複数のドメインオブジェクト間の相互作用を管理します:
- 目的: エンティティ間の複雑な相互作用を調整
- 特徴: 多くの場合、関係の整合性を維持
- サービス例:
InventoryAllocator
、ScheduleCoordinator
、AccountReconciler
- 操作例:
allocateInventory()
、scheduleAppointment()
、reconcileAccounts()
ドメインイベントサービス
ドメインイベントサービスはドメインイベントの作成と処理を管理します:
- 目的: ドメインイベントの作成、充実、または処理
- 特徴: 多くの場合、ドメインイベントシステムと連携
- サービス例:
OrderEventService
、UserActivityTracker
、NotificationService
- 操作例:
recordOrderPlaced()
、trackUserAction()
、createNotificationEvents()
Prism Architectureにおけるドメインサービス
アーキテクチャ内の位置
ドメインサービスはPrism Architectureのドメイン層に存在します。その配置にはいくつかの意味があります:
- 純粋なドメイン重視: ドメインロジックのみを含み、インフラストラクチャやアプリケーションの懸念事項は含まない
- コア層への依存: コア層からのエンティティと値オブジェクトにのみ依存
- 外向きの依存関係なし: オーケストレーション層やインフラストラクチャ層に依存しない
- アクセス性: ユースケースを実装するためにオーケストレーション層によって使用される
この配置により、ドメインサービスは技術的な懸念事項に汚染されることなく、ビジネスルールに純粋に焦点を当てることができます。
他のコンポーネントとの関係
ドメインサービスは他のコンポーネントと特定の方法で相互作用します:
- エンティティと値オブジェクト: ドメインサービスはコア層からのエンティティと値オブジェクトを操作する
- リポジトリ(インターフェイス): ドメインサービスはコア層で定義されたリポジトリインターフェイスを使用することがある
- ユースケース: オーケストレーション層のユースケースはビジネスロジックを実装するためにドメインサービスを呼び出す
- ドメインイベント: ドメインサービスは重要な変更を通知するためにドメインイベントを生成することがある
ドメインサービスはデータベース、API、ファイルシステムなどのインフラストラクチャコンポーネントに直接アクセスすべきではありません。外部依存関係へのアクセスは、コア層で定義され、インフラストラクチャ層で実装されたインターフェイスを通じて行われるべきです。
ドメインサービスと他のサービスタイプの比較
ドメインサービスが他のタイプのサービスとどのように異なるかを理解することは、適切な関心の分離を維持するのに役立ちます。
ドメインサービス vs. アプリケーションサービス
アプリケーションサービス(Prism Architectureのユースケースなど)はドメインサービスと異なります:
ドメインサービス | アプリケーションサービス(ユースケース) |
---|---|
ビジネスルールとロジックに焦点 | オーケストレーションと調整に焦点 |
純粋なドメイン概念 | 技術的な懸念事項を含む場合がある |
インフラストラクチャ依存関係なし | インフラストラクチャコンポーネントを使用する場合がある |
ユースケースフローに関心がない | アプリケーションフローとユーザー相互作用を管理 |
アプリケーションサービスによって使用される | ドメインサービスを使用する |
ドメインサービス vs. インフラストラクチャサービス
インフラストラクチャサービスは技術的な機能を提供し、ドメインサービスとは大きく異なります:
ドメインサービス | インフラストラクチャサービス |
---|---|
ビジネスロジックを実装 | 技術的な機能を実装 |
ドメイン用語 | 技術的な用語 |
外部依存関係なし | 外部システムと統合 |
永続化に関する知識なし | 多くの場合、永続化の詳細を扱う |
アプリケーション層によって使用される | 複数の層によって使用される |
ドメインサービス vs. ドメインエンティティ
ドメインサービスとエンティティはどちらもドメインモデルに存在しますが、異なる特徴を持っています:
ドメインサービス | ドメインエンティティ |
---|---|
プロセスまたは操作を表す | ドメインオブジェクトまたは概念を表す |
通常ステートレス | アイデンティティと状態を持つ |
アクティビティやプロセスにちなんで命名 | モノや名詞にちなんで命名 |
振る舞いに焦点 | データと振る舞いの両方に焦点 |
複数のエンティティに対する操作 | 自身の状態に対する操作 |
効果的なドメインサービスの設計
識別戦略
ドメインサービスを作成するタイミングを特定するには、以下の指標を探します:
- 複数エンティティ操作: 複数のエンティティにまたがるロジック
- プロセスであってものではない: エンティティに自然に属さないアクティビティ
- ステートレス操作: 呼び出し間で状態を維持する必要のないロジック
- ドメイン専門家の言語: ドメイン専門家がものではなくプロセスについて話す場合
- 複雑なルール: エンティティ境界を超えて適用されるビジネスルール
設計原則
ドメインサービスを設計する際には、以下の原則に従います:
- ドメイン駆動の名前: ドメインのアクティビティとプロセスを反映する名前を使用
- 動詞ベースのインターフェイス: ドメイン内のアクションを表現するメソッドを定義
- 明示的なパラメータ: すべての依存関係をメソッドパラメータとして明示的に
- 副作用よりも戻り値: 状態を変更するよりも結果を返すことを優先
- 不変性: 可能な場合は不変のパラメータを使用し、不変の結果を返す
- ドメイン固有のエラー型: 技術的な例外ではなくドメイン固有のエラー型を使用
インターフェイス設計
よく設計されたドメインサービスインターフェイスは:
- ドメインの意図を表現: サービスがドメイン用語で何をするかを明確に伝える
- 実装の詳細を隠す: 複雑な実装の詳細を抽象化する
- ドメイン型を使用: プリミティブではなくドメインオブジェクトを受け取り返す
- 関連する操作をグループ化: 関連する操作を同じサービスにまとめる
- 凝集性の原則に従う: サービス内のすべての操作が同じドメイン概念に関連していることを確認
実装パターン
ドメインサービスを実装するための一般的なパターンには以下があります:
- ストラテジーパターン: 交換可能なビジネスルールや計算用
- 仕様パターン: 複雑で構成可能なバリデーションルール用
- デコレーターパターン: サービスにクロスカッティングコンサーンを追加するため
- 責任連鎖パターン: 順次処理ステップ用
- ビジターパターン: 複雑なオブジェクト構造全体の操作用
ドメインサービスのテスト
ドメインサービスのテストは、純粋なビジネスロジックに焦点を当てているため、通常は簡単です:
単体テスト
ドメインサービスは単体テストに非常に向いています:
- 純粋なロジックテスト: ビジネスルールと計算を直接テスト
- モックが不要: 多くの場合、モックがほとんどまたはまったく必要ない
- パラメータ化テスト: 異なるシナリオにパラメータ化テストを使用
- 境界テスト: 境界条件とエッジケースをテスト
- 期待値と実際値: 期待される出力と実際の結果を比較
振る舞いテスト
より複雑なドメインサービスの場合、振る舞いテストが適切かもしれません:
- Given-When-Then: 振る舞い駆動テストアプローチを使用
- ビジネスシナリオテスト: 完全なビジネスシナリオをテスト
- ドメインイベント検証: 適切なドメインイベントが生成されることを検証
- 状態変更検証: エンティティの状態が期待通りに変更されることを検証
一般的な落とし穴とアンチパターン
ドメインサービスのアンチパターン
ドメインサービスを設計する際には、以下の一般的なアンチパターンを避けてください:
- 貧血サービス: 振る舞いを持たず、単なるデータキャリアであるサービス
- 神サービス: あまりにも多くの責任を扱おうとするサービス
- インフラストラクチャの漏洩: ドメインサービスにインフラストラクチャの懸念事項を含める
- 手続き型サービス: ドメイン概念ではなく手続き型ロジックを実装するサービス
- サービス内のエンティティ振る舞い: エンティティ内ではなくサービス内にエンティティの振る舞いを配置する
- 技術的サービス: ドメインではなく技術的な懸念事項に焦点を当てたサービス
問題のあるドメインサービスの兆候
問題を示す可能性のある以下の警告サインに注意してください:
- 技術的な名前: 技術的な懸念事項にちなんだ名前を持つサービス(例:
DatabaseService
) - CRUD操作のみ: CRUD操作のみを実行するサービス
- インフラストラクチャ依存関係: インフラストラクチャコンポーネントへの依存関係を持つサービス
- 大きなインターフェイス: 多くの関連のないメソッドを持つサービス
- プリミティブパラメータ: ドメインオブジェクトではなく主にプリミティブを扱うサービス
ドメインサービスの例
例1: 価格サービス
価格サービスは複雑な価格ルールを処理します:
インターフェイス:
- calculatePrice(product, quantity, customer, promotions) -> Price
- calculateDiscount(product, customer, promotions) -> Discount
- determineTaxability(product, customer, location) -> Taxability
このサービスは、複数の要因に依存し、単一のエンティティに属さない価格ロジックをカプセル化します。
例2: 在庫割り当てサービス
在庫割り当てサービスは在庫管理を処理します:
インターフェイス:
- allocateInventory(order, inventory) -> AllocationResult
- releaseAllocation(allocation) -> ReleaseResult
- checkAvailability(products, quantities) -> AvailabilityResult
このサービスは、複数の製品と場所にまたがる在庫割り当てを調整します。
例3: 支払い処理サービス
支払い処理サービスは支払い操作を処理します:
インターフェイス:
- processPayment(order, paymentMethod, amount) -> PaymentResult
- refundPayment(payment, amount, reason) -> RefundResult
- authorizePayment(order, paymentMethod, amount) -> AuthorizationResult
このサービスは複数のエンティティにまたがる支払いロジックをカプセル化します。
ドメインイベントとの統合
ドメインサービスは多くの場合、ドメインイベントの生成と処理において重要な役割を果たします:
イベントの生成
ドメインサービスは重要な変更を通知するためのドメインイベントを生成することがあります:
- ビジネスプロセスイベント: ビジネスプロセスの完了を通知するイベント
- 状態変更イベント: 重要な状態変更を示すイベント
- ポリシー違反イベント: ビジネスポリシーの違反を通知するイベント
イベントの処理
ドメインサービスはドメインイベントを処理することもあります:
- カスケードアクションのトリガー: イベントに応じて追加のアクションを実行
- 派生状態の更新: イベントに基づいて派生または計算された状態を更新
- 集約間の一貫性: 集約境界を超えた一貫性を維持
結論
ドメインサービスはPrism Architectureのドメイン層の重要なコンポーネントであり、エンティティ内に自然に適合しない重要なビジネスプロセス、計算、バリデーション、調整ロジックをカプセル化します。ドメインサービスを適切に特定し実装することで、ビジネスドメインを正確に表現するより表現力豊かで保守性の高いドメインモデルを作成できます。
ドメインサービスはインフラストラクチャやアプリケーションの懸念事項なしに、純粋にドメインロジックに焦点を当てるべきであることを覚えておいてください。ドメインサービスはドメイン概念を明確に表現し、ドメイン用語を使用し、ドメインオブジェクトを操作する必要があります。この文書で説明されている原則とパターンに従うことで、ドメインモデルを強化し、アプリケーションのビジネスロジックをサポートする効果的なドメインサービスを作成できます。
次のステップ
- ドメインイベント - ドメインイベントによるリアクティブプログラミングについて学ぶ
- ドメインモデル設計 - ドメインモデル設計のための高度なパターンを探る
- ユースケースとの統合 - ドメインサービスがユースケースとどのように統合されるかを確認する
- リポジトリ実装 - リポジトリがデータアクセスをどのように実装するかを理解する