blog.bouzuya.net

2024-05-23 DDD の Repository に create / delete / update を追加すべきでない理由

DDD の戦術的パターンの Repository で create / delete / update を追加すべきでない理由についてチームメンバーと話した。それを簡素化したものをここにも書いておく。

ぼくは Repository の更新処理は savestore の一方に絞るようにしている。

このメソッドは第一引数に楽観排他制御用の version を optional で取り、指定があれば新規作成、なければ更新をする。つまり repository.store(None, &aggregate)createrepository.store(Some(before.version()), &aggregate)update ということだ。

これを見て「第一引数が None か Some かで createupdate かが決まるならメソッドを分けても同じこと」という意見が出る。だいたい出る。これは部分的に正しい。

正しくない点 (問題) は createupdate があると delete などを追加する奴が出てくることだ。ぼくの経験上はまず間違いなく出てくる。 CRUD なんて単語があるくらいだし、 SQL でも INSERTUPDATEDELETE があるから自然な流れとして出てくる。さらに Repository に delete を追加する人間はだいたい引数を aggregate_id で十分とする。集約を識別できれば、あとは削除するだけだと考える。 DELETE FROM aggregates WHERE id = ? だからね。

なぜこれが正しくないのかは、これをやると Aggregate には存在しない delete という操作が Repository には存在する状態になるからだ。

Aggregate の削除イベント (≒削除操作) をモデリングしてドメイン層に加えようとすれば、これが間違いだと気づくはずなのだけど、 IO や RDBMS を中心に考えたり、そういうフレームワークに慣れているとよく間違えられる。

Repository に delete が入ると、さらに個別の更新操作なども Repository に追加されはじめたりする。気づけば Aggregate はただの構造体でほとんどの処理が Repository にある状態ができあがる。

こうなってしまうともはや DDD の戦術的パターンを適用する意味はない。……というか適用できていない。

Repository の役割は Aggregate の状態を永続化 (再構築可能に) することであって、操作を持つことではない。ドメイン層と永続化層を明確に分離したいなら、そこを破壊することにつながる命名は避けるべきだと思う。

またぼくは Repository (のインタフェース) をドメイン層ではなくアプリケーション (ユースケース) 層に置くようにしている。それは↑のような話をしたときに、ドメイン層に Repository のインタフェースがあることで「ドメイン層だから Repository に処理があっても問題ない」という判断に至る可能性があるからでもある。

……ここまで書いておいてあれだが、そもドメイン層と永続化層を明確に分離するメリットがない場合もある。ここに書いた「 Repository に create / delete / update を追加すべきでない」は冒頭に書いた通り DDD の戦術的パターンの Repository の場合の話でしかない。 CRUD に特化して Model が Record と一体化しているようなフレームワークを使うと効果的な場面ではそれを使えばいい。状況に応じて何が正しいかは変わる。


今日のコミット。