【学び】CQRS(DDD)
DDDにおける参照系の複雑さ
DDDで定義されている実装パターンを使っていると、基本的には永続化層との入出力はRepositoryを使うことになります。 更新系の処理ではEntityやValueObjectでドメインの知識を表現し、Repositoryを使って集約単位で永続化するという構成をとると、非常にメンテナンス性の良いものになります。
更新系(書き込み系)に関してはこれで上手くハマるが、参照系(読み込み系)で複数集約に跨ったデータを取得する場合には厄介な問題が生じる。
TaskListUseCase
↓
TaskRepository
& UserRepository
& LabelRepository
でそれぞれでデータを取得
(※リレーションで引っ張ってくるという考え方が使えないのはリポジトリパターンの重要な点)
↓
TaskListUseCase
内でそれぞれの値を良い感じにまとめて返却する
CQRS(コマンドクエリ責務分離)
CQRSとは、「情報の参照に使用するモデルと更新に使用するモデルに異なるものを使用する」というアーキテクチャです。
モデルという言葉は多義語ですが、この文脈ではアプリケーションコード上のモデル、つまり更新系のオブジェクトと参照系のオブジェクトを分けるということになります。
✏️ ※謎にmermaid頑張った人の図😇。Read時のDB→Query→UseCase
の部分だけどうしても再現できなかった…
名前 | 具体例 | UseCaseがDBアクセスする際に使用するもの |
---|---|---|
更新系モデル | DDDのEntity、ValueObjectなど(Write Model) | Repository |
参照系モデル | 特定のユースケースに特化した値の型。SQLの結果1レコードを1つの型にするなど (DTOといった命名をする)(Read Model) | 専用のオブジェクト(QueryServiceといった命名をする) |
✏️ なるほど、ちょこちょこ聞いたことがあった「DTO(Data Transfer Object)」はこの文脈における話だったのか。
参照系モデル(Read Model)とQuery Service
DDDのアーキテクチャ上どのように位置付けられるかというと、QueryServiceのInterfaceと戻りの型をUseCase層に、実装クラスはRepositoryと同様にインフラ層に配置します。UseCaseからは、「このような条件を指定すると、このような型で返ってくる」というという抽象的な知識だけ持ち、実装の知識はインフラ層に隠蔽します。
✏️ Interfaceをドメイン層じゃなくてユースケース層に置くのもQueryServiceのポイント。
その理由は、
QuerySerivceの戻り値がユースケースに依存したものであるからです。
✏️ 「タスク一覧の取得」と一口に言っても、「全ユーザーのタスク一覧を取得する」ユースケースと、「自分のタスク一覧を取得する」ユースケースと、「全ユーザーのタスク一覧取得するユースケースの中でも特定の値のみが必要なユースケース」などと言ったように、ユースケースごとに参照(取得)したい値は変わる。つまり、そのユースケースごとに型は存在しているべき。
そのため、「タスク」というドメイン自体のルール・制約を記述するドメイン層にインターフェースを置くのではなく、ユースケース層にインターフェースを置くのが最適解となる。
例
public class TaskDto {
private String taskId;
private String taskName;
private String userName;
private String labelName;
}
public interface TaskQueryService {
public List<TaskDto> fetchByUserId(UserId userId);
}
出典:参考記事(https://little-hands.hatenablog.com/entry/2019/12/02/cqrs)
QueryServiceの実装クラス内では(今回の事例では)複数のテーブルをJoinして一発で取得するクエリを書き、シンプルに結果をDTOに詰め替えます。クエリ実行に使用する方法は必要に応じて選択できます。直接StringでSQLを書いても良いし、クエリビルダのようなライブラリを使用しても良いです。
✏️ Active Recordのパターンと違って、リレーションを用いてデータを引っ張ってくることが出来ないので、各テーブルをJOINして取得するのが一般的みたい。
✏️ CQRSの導入はメリットは大きい分、コードの複雑性を上げてしまうとのこと。