この記事は
前回記事 - 「ドメイン駆動設計入門」を読んでのつづき
リポジトリとアプリケーションサービスについてまとめる
リポジトリ
リポジトリは、オブジェクト(データ)を永続化し、また再構築する
大事なのは、ドメインがデータストアの実態を知らなくてよいということ
ドメインはリポジトリに依頼するだけでよい
ドメインサービスの見通しをよくするリポジトリ
値オブジェクトやエンティティの補助となるドメインサービス
たとえば、あるエンティティがすでに存在するか確認するためには
データストアへの問い合わせを記述せねばならず、ドメインサービスのコード量が増える
見通しをよくするための方法
インターフェイスをつかって任意のリポジトリを注入することで
データストアの実態によらず、すっきり簡潔に書ける
class SomeDomainService
{
private IRepo repo;
public SomeDomainService(IRepo repo) // コンストラクタインジェクション
{
this.repo = repo;
}
public void OtherMethod()
{
// this.repo を使った何らかの処理
}
}
リポジトリのインターフェイス
interface IRepo
{
Obj Find(ObjName name)
void Save(Obj obj)
Option<Obj> Find(ObjName name)
// リポジトリの責務は、永続化と再構築である
bool Exist(ObjId id) // <- オブジェクトの有無を確認するのは責務範囲外ともいえる
}
サンプルコードの実現方法
データ永続化の手段が3つ用意されている
インメモリで動作するモジュール
SQL 文を利用したモジュール
Entity Framework を用いたモジュール
JSONファイルの一部を書き換えることで、コンフィグがスイッチするみたいだけど
コンフィグの中のセットアップ処理についてはよくわからない・・
"Dependency": {
"setup": "EFDependencySetup"
},
アプリケーションサービス
アプリケーションサービスはドメインオブジェクトを協調させてユースケースを実現します (本書 P113)
ドメインモデルをドメインオブジェクトで表現できたら
次は目の前にある実問題を解決するためにアプリケーションサービスを構築する
ドメインサービスのおさらい
似たような言葉ですでにドメインサービスがある
ドメインサービスはドメインオブジェクトに対するサービスである
- 値オブジェクトやエンティティがもつと不自然になるふるまいを代行する
- まずは値オブジェクトやエンティティにふるいまいをもたせることを検討する
- 自身のふるまいを変更するような状態をもたない
ユースケースを組み立てる
QiitaなどSNSでは以下のようなユースケースが想定される
ドメインオブジェクトを用意する
GitHubに本書のリポジトリがあるので、そちらを参考にドメインオブジェクト群をつくる
ドメインオブジェクトは材料でしかない
かき集めたこれらの材料を利用するのはアプリケーションサービスである
アプリケーションサービスをつくる
UserApplicationService.csにRegister
やDelete
など、ユースケースに沿ったメソッドが用意されている
そしてそれらのメソッドはドメインオブジェクトのインスタンスであるuserService
やuserRepository
などを利用している
ドメインオブジクトの公開範囲
「登録者情報を確認したい!」の文言のとおりに、そのままドメインオブジェクトであるユーザーインスタンスを戻り値で渡した場合、予期せぬふるまいまでされてしまう危険性がある
(単純に登録情報を閲覧するためだけにゲットしたuser
のchageName()
メソッドが呼ばれた場合など)
DTO(Data Transfer Object) を使うことで、ドメインオブジェクトを直接渡さずに済ませられる
リポジトリから見つけたユーザー(ドメインオジェクト)をUserData
(DTO)に詰め替えて、さらにUserGetResult
に詰め替えて戻り値としている
こうすることで、必要最低限の情報のみを外部へ公開している
// UserApplicationServiceのGetメソッド
var user = userRepository.Find(id);
var data = new UserData(user);
return new UserGetResult(data);
ユーザー情報を更新するコマンド
登録情報を更新するUpdate
処理を考える
リリース当初はUpdate(アカウント名)
だけでよかったかもしれない
数か月後、Update(アカウント名、メールアドレス、性別)
となり
数年後、Update(アカウント名、・・・たくさんの引数・・・)
となる
将来引数が増えていくやもしれぬ処理については
ファサードとなるコマンドを導入することで対処する
UpdateCommand
のコンストラクタに変更したいパラメータを渡す
変更したくないパラメータについてはnull
のままとしておく
// コマンドのコンストラクに
UpdateCommand(arg1 = null, arg2 = null, arg3 =null)
{
}
// 更新処理
public void Update(UserUpdateCommand command)
{
if(command.arg1 != null)
{
// 更新処理
}
}
登録処理と退会処理をわける
UserApplicationService
はもっと細分化できる
登録や更新では、ドメインサービスuserService
に重複確認を行う
一方、退会Delete
では重複確認の必要はない
凝集度の指標として、そのクラスのメソッドがすべてのインスタンス変数を使っていることが目安になる
UserApplicationService
をUserRegisterService
とUserDeleteService
にわけることで、より高い凝集度のクラスをつくることができる
アプリケーションサービスのインターフェイス
interface IUserSomeService
{
void Handle(SomeCommand command)
}
サービスとはなにか
- サービスは自身のためのふるまいをもたない
- サービスはものごとではなく、活動や行動である
- ドメインサービスとアプリケーションサービスの本質は同じ
- ドメインの知識を表現したものがドメインサービス
- 利用者の問題を解決するものがアプリケーションサービス
アプリケーションサービスのまとめ
とにかくドメインの知識がアプリケーションサービスに漏れ出ないようにすること!