昨日の記事でも触れたとおり、ここ最近iDDDの本を読んでいたのですが、なかなかアプリケーションサービスとドメインサービスの違いが理解できなかったので備忘録的にまとめてみました。
ドメインサービスの責務
- ドメインサービスはドメインモデルにおける、重要な処理を担ったり、他のドメインオブジェクトの変換や、複数のドメインオブジェクトの値を利用し計算するなどの用途に適している。
- 特にEntityオブジェクトやValueObjectで上記を実装すると記述量が増えてしまう場合などに有効。
- ドメインサービスのメソッド名はユビキタス言語を表すようにし、状態を持たない。
- とはいいつつ、Repositoryなどをコンスタラクタの引数として受け付けて、それを内部のプロパティとして保持するパターンがiDDDの例でもいくつか見られる(AuthenticationServiceやAuthorizationService)。
- 上記の例では特にRepositoryオブジェクトがドメインサービスオブジェクトに引き渡され、DBからのデータの取得などが委譲されている(下記参照)。なお、この例ではORMとしてHibernateが使われている。
- ただし、上記の場合はドメインモデル層にレポジトリ用のインターフェースを定義し、インフラ層内で実装する。つまりセパレートインターフェースを使う戦略。こうすることで、ドメイン層とインフラストラクチャ層の依存関係を逆転させ、インフラストラクチャ層のレポジトリはドメイン層で定義されたインタフェースに従って実装することになる。
- そうすることでDBのミドルウェアが変わっても、ドメインモデル側からは決まったメソッド名で要求を実現でき、ドメインモデル内のコードの変更が必要ない状態にできる。
package com.saasovation.identityaccess.domain.model.identity;
import com.saasovation.common.AssertionConcern;
public class AuthenticationService extends AssertionConcern {
private EncryptionService encryptionService;
private TenantRepository tenantRepository;
private UserRepository userRepository;
public AuthenticationService(
TenantRepository aTenantRepository,
UserRepository aUserRepository,
EncryptionService anEncryptionService) {
super();
this.encryptionService = anEncryptionService;
this.tenantRepository = aTenantRepository;
this.userRepository = aUserRepository;
}
//以下省略
}
アプリケーションサービスの責務
概要
- アプリケーションサービスはドメインオブジェクトを使ってアプリケーションの要求を実現することが目的。
- ただし、ドメインモデル内のビジネスロジックには立ち入らず、「タスクの調整」に留める。
- レイヤードアーキテクチャで言うところの「アプリケーションレイヤー」の責務とはちょっと異なる場合があると思っている。
- 例えばレイヤードアーキテクチャではドメインモデルが利用できるようにパラメータなどの外部入力を安全な形に変換する責務はアプリケーションレイヤに委譲されていたが、ヘキサゴナルアーキテクチャではアダプターの責務になっており、アプリケーション層はまさにアプリケーションの要求の実装に関心を寄せている。
プレゼンテーションに関して
- (i)DDDでのプレゼンテーションにはいくつかパターンがありそう。
- ひとつは、アプリケーション層でドメインモデルをプレゼンテーションモデルに変換し、アダプターに返す。具体的な描画に関する実装はアダプターに委ねる。
- 例えばDPO(Domain Payload Object)やDTO(Data Transformer Object)を使って集約やエンティティを表示する場合はアプリケーションサービス内でDPOやDTOを作成する。
- その際mediatorやダブルディスパッチを使うことも検討せよと言っている。
- Cockburnのヘキサゴナルアーキテクチャでは表示用のデータの作成アダプターの責務のように描かれているので、上記とはちょっと矛盾しているが、特に詳しく説明されているわけでないので、解釈の余地はおおいにある(下記のサンプルコード参照)。
- iDDD のヘキサゴナルなアーキテクチャであればアプリケーション層に、outputPortオブジェクトが引き渡され、アプリケーションは常にoutputPortをwriteするようにする。つまりアプリケーション層にプレゼンテーションの責務を負わせる、ように見えるが実際アプリケーションはoutputPortのインターフェースを使っているのみで、プレゼンテーションの実装はやはりAdapter内のプレゼンテーション層。
- このへんはクリーンアーキテクチャのほうでより洗練されている印象。
Cockburnのヘキサゴナルアーキテクチャの例
CockburnさんのAdapterのサンプルコードでは、アプリケーション処理の直後からプレゼンテーションの処理が始まっている。
rack_http_adapter.rb
class RackHttpAdapter
def initialize(hex_app, views_folder)
@app = hex_app
@views_folder = views_folder
end
def call(env) # hooks into the Rack Request chain
request = Rack::Request.new(env)
value = path_as_number(request)
rate, result = @app.rate_and_result value
# このへんからプレゼンテーションの処理
out = {
out_action: 'result_view',
value: value,
rate: rate,
result: result
}
template_path = Pathname.new(@views_folder).join(out[:out_action]).sub_ext('.erb')
page = html_from_template_file(template_path , binding)
response = Rack::Response.new
response.write(page)
response.finish
end
# 以下省略
end
ユースケースに関して
- アプリケーションサービスは一つのユースケースのフローごとに一つのメソッドを持つ
- アプリケーションにもインターフェースを定義して、アダプターがそれを使って異なるアプリケーションを同じように使えるようにすることもできる。
- また、アプリケーションサービスの引数に異なるデータトランスフォーマーを指定することで、ユースケースを変えずに、アプリケーションの挙動をごっそり変えることもできる。この場合、アダプターがアプリケーションに依存している関係が変わるわけではないので、依存関係の逆転は発生しない。また、依存の方向も外から内に向いているので、逆転させる必要もない。よってこの場合はセパレートインターフェースも必要ではない。
まとめ
- いろんな人がいろんなことを言っているのでややこしい。特にヘキサゴナルについては本来のヘキサゴナルとDDDでのヘキサゴナルでちょっと構造が違う気がする。
- 特にプレゼンテーションに関する責務や設計パターンはまだ自分でもこんがらがってる