アプリケーションサービスとドメインサービスの違いと比較
「値オブジェクト」や「エンティティ」には振る舞いが記述される。
しかし、それらのドメインオブジェクトでは表現しにくいオブジェクトも存在する。
そういったものを表現するのに適したオブジェクトが「ドメインサービス」になる。
用途は?
Repositoryを使う処理
ユーザーの重複を確認するコードなどは、Userクラスに書くのはUser.exists
などとなって不自然になる。UserService
などのドメインサービスを定義してそこにexistsメソッド
を追加する。
基本的に、重複チェックなどの入出力(DBからデータの取得)などが伴う処理はアプリケーションの関心ごとであり、ドメインサービスに記述するのはあまり望ましくないと言われているが、重複チェックなどはドメインに関連する
ことなので記述するのも良いと考える。
複数のドメインオブジェクトを使った処理
複数の関連するエンティティを使って計算する処理などもアプリケーションサービスやエンティティではなく、ドメインサービスに記述した方が良い。
特徴
値オブジェクトやエンティティと異なり「ドメイン特有のインスタンス変数」
を持たない。
あくまでドメインサービスは振る舞いに特化したクラス
になる。
ただし、ドメイン特有のインスタンス変数は持たないが、「アプリケーション特有のインスタンス変数」
は持つ。(DBに接続するためにRepositoryを持つなど)
注意点
なんでもかんでもドメインサービスに記述しない
例えば、ユーザーの名前を変更するChangeName
メソッドなども全てドメインサービスに記述できてしまう。
しかし、なんでもドメインサービスに記述するのではドメインオブジェクトの利点を活用しきれておらず、ユーザー名の変更などの自然な振る舞いはクラスから分離して、
クラスを見ただけですぐに判断できる状態が望ましい`。
ドメインサービスから別のドメインサービスを切り出すことも考える
例えば、部署クラスの中に、別の部署に荷物を配送する処理(例えば、配送メソッド)
を記述することになったとする。
配送処理の中でユーザーに直接関係ないような複雑な処理などを記述することになりそう
であるならば、配送クラスとして別のドメインサービスとして切り出すことも検討する。
どこから呼び出すのか
基本的にはアプリケーションサービスから呼び出すことになり、プレゼンテーション層から呼び出すものではない。
アプリケーションサービスの責務
アプリケーションサービスはドメインオブジェクトを使ってアプリケーションの要求を実現することが目的。
しかし、ドメインモデル内のビジネスロジックには立ち入らず、「タスクの調整」
に留める。
アプリケーションサービスはドメイン層を使うアプリケーション層に含まれ、トランザクションやセキュリティのような調整的な処理
を行う。
アプリケーションサービスを活用する例として、コンテキストの統合時に使われる「変換サービス」も存在する。
他のコンテキストと連携し、腐敗防止層
を実装する際に、専用のアダプターと変換サービスを用い、ドメインモデルの変換を行う場合がある。
この詳細は、境界づけられたコンテキストの統合
にて紹介。
アプリケーションサービスかドメインサービスのどちらに実装するべきか迷った場合
境目は、ビジネスロジックの複雑さや、それらがどのような役割を持つかによって決まる。
以下のような基準を考慮して、アプリケーションサービスとドメインサービスの境目を決定できる。
ビジネスロジックの複雑さ
複数のエンティティ間で単純なやり取りがあるだけであれば、アプリケーションサービスに記述しても問題はない。
しかし、複雑なビジネスロジックやドメインの知識が必要な場合
は、ドメインサービスにそのロジックを記述する
ことが適切になる。
ドメインの知識と責任
エンティティ間のやり取りがドメインの知識や責任に関連している
場合は、ドメインサービスに記述すべきと考える。
これにより、ドメイン層がビジネスルールをカプセル化し、アプリケーションサービスがドメイン層に依存することで、設計の一貫性が保たれる。
再利用性
ドメインサービスに記述されたビジネスロジックは、他のアプリケーションサービスからも利用できる
ようになる。
同じビジネスロジックが複数のユースケースで利用される
場合は、ドメインサービスに記述することが適切。
最終的には、開発チームの経験や判断が重要となる。
複数のエンティティ間のやり取りが多く、ドメインサービスだらけになることを懸念している場合は、ビジネスロジックの分割や整理を検討することで、設計のバランスを保つことができる。
重要なのは、ビジネスロジックの責任範囲を明確にして、適切な層に配置する
こと。
ドメインサービスの使い所として、そのふるまいが以下のいずれかに該当するかを判断基準としているとのこと。
- 複数集約間で整合性を担保する必要があるか
- 単一集約でも複数インスタンスを扱うか
- 実装したいドメインルールが外部サービスと連携する表現をした方が自然か
そしてなにより大事なのは、ビジネスルールとして名前がつけられるかどうか
とのこと。
具体例
- 口座間の資金振替
- (Evans本)
- 資金振替 のために 引き落とし と 預け入れ を行う
- ユーザー認証における仕組みの隠蔽
- (Vernon本)
- テナントの有効状態確認、認証、パスワード暗号化 の知識をクライアント側から追い出す
- メールアドレス重複チェック
- (「ドメイン駆動設計 モデリング/実装ガイド - little-hands - BOOTH」)
- ユーザーオブジェクト自身 は 他のユーザー の情報を持っていない
- 拠点間の輸送
- (「ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本(成瀬 允宣)|翔泳社の本」)
- 輸送 は必ず 出庫 と 入庫 のセットで行われる
これらを俯瞰してみると、以下のようなケースに使われることがあるとのこと
- 単一/複数集約の複数インスタンス間にまたがって実現するケース(資金振替、メールアドレス重複チェック、拠点間の輸送)
- その処理過程・順序そのものがビジネスルールであるケース(認証の仕組み隠蔽)
- インタフェースを通じてインフラ層のふるまいと組み合わせるケース(メールアドレス重複チェック)
ドメインサービスの失敗例
次に、ドメインサービスを導入するときの注意点。
ドメインサービスの多用/誤用
ドメインサービスの危険な点は、ユビキタス言語ではないビジネスロジックを大量に記述
できてしまう点。特にDDDに慣れていない場合、トランザクションスクリプトで処理を書いてしまう
可能性がある。
この問題は、使う必要がない箇所でドメインサービスを利用してしまうため、エンティティや値オブジェクト
が空っぽの「ドメインモデル貧血症(5章)」を引き起こす危険性がある。
ドメインサービスのミニレイヤ
似たアンチパターンとして、ドメインサービスの「ミニレイヤ」
を作ってしまう失敗がある。
検討することなく最初からドメインサービスのレイヤを作ってしまうと、このレイヤが肥大化する
傾向にある。
それを避けるため、ドメインサービスのレイヤを作る必要がないコンテキストでは導入しないようにする。
アプリケーションサービス層での実装
別の失敗として、ドメインサービスをアプリケーションサービスで実装してしまう
間違いもある。
アプリケーションサービス
は、トランザクションやセキュリティといった、ドメインの外側の関心ごとの実装を行う
場所。
ドメインサービスの設計方法
- セパレートインターフェイスが必要かどうか検討
- サービスの生成方法を検討
セパレートインターフェイスとは
セパレートインターフェイスとは、マーチン・ファウラー氏が、書籍「エンタープライズ アプリケーション アーキテクチャ(PofEAA:2005年)」にて紹介しているパターン。
実装クラスとは別のパッケージでインターフェイスを定義することを「セパレートインターフェイス」
と呼ぶ。
上図では「IEncryptionService」がインターフェイス
で、「MD5EncryptionService」が実装クラス
です。
このパターンのメリットは、実装クラスもクライアントもインターフェイスに依存する
ため、依存関係が複雑にならない。