はじめに
集約がまじでわからん。
ということで、自分の理解のために考えてみた。
注:間違っている可能性が高いです
集約の意義
サークルにはメンバーが加入でき、加入できる人数は最大10人まで、みたいなルールがあった場合に
エンティティとしてはサークル has many メンバーみたいな感じ
で、サークルにメンバーを加入するロジックとしては
circle.members.add(user)
ただこれだと、利用側で人数に関するバリデーションをかけなければならず、
if circle.members.count > 10 {
"人数が上限を上回っています"
}
同じロジックが複数箇所に書かれてしまう。
なので、サークルを集約ルートとして、人数に関するルールはサークルに実装することで、ロジックの散在を防ぐことができる。
circle.join(user)
と、この辺までは教科書的な説明で簡単。
難しいのは、repositoryとかが絡んできたとき。
集約とrepositoryについて深堀
あるサークルにメンバーを追加したいとする。
テーブル的にはclrcle_membersにレコードを作成することになる。
(circle_idとuser_idをもっている)
この場合、circle_idとuser_idを受け取ってcircleMemberRepsitoryに渡すべきか?
ただそうすると、先程の人数に関するルールが守られない可能性がある。
circle_member = new CircleMember(circle_id, user_id)
circleMemberRepository.save(circle_member) # 10人以上保存できてしまう
常にルールを守らせるようにするには、circleを通して変更する必要がある。
では、どうするか?
circle = circleRepository.find(circle_id)
member = userRepository.find(user_id)
circle.join(member)
circleRepository.save(circle)
つまり、circleRepositoryで紐づくmemberも保存すべし、となる。
まとめると、repositoryを集約単位で作ることで、集約に関するルールを常に守らせることができる。
別の例。
ユーザが複数のメールアドレスを登録できるが、最大3つまで、みたいなルールがあったとする。
この場合の集約ルートはユーザということになる。
テーブル構成的にユーザテーブルに3つのカラムとしてアドレスを含めることもできるが、
アドレスを登録するタイミングがユーザ作成のタイミングと異なり1個1個登録する場合は
ユーザメールアドレスというテーブルを作ることも考えられる。
今回はそのケースで考える。
先程の教訓から行くと、メールアドレスを追加したい場合はuserRepositoryを通して行う、ということになる。
user = userRepository.find(user_id)
user.addEmail(address)
userRepository.save(user)
この場合、userRepository.findで紐づくメールアドレスも取得し、
userRepository.saveで紐づくメールアドレスも保存する、となる。
紐づくメールアドレスも保存する、の部分について詳しく。
上記の場合であれば、追加されたメールアドレスだけ保存すればよいが、
UI上複数のメールアドレスを一括で登録したい、みたいな要件があった場合。
(常にユーザが入力したメールアドレスのセットを正として洗い替えを行うとする)
user = userRepository.find(user_id)
user.replaceEmails(addresses)
userRepository.save(user)
この場合は、userRepository.save(user)
の中で紐づくメールアドレスのdelete-insertを行う。
別のユースケースで、単純にユーザの更新のみを行いたい場合。
user = userRepository.find(user_id)
user.name = "hoge"
userRepository.save(user)
この場合、userRepository.find(user_id)
の時点で紐づくメールアドレスも取得しているので、
userRepository.save(user)
で無駄なアップデート処理が走るが、実害はない。
もしもuserRepository.find(user_id)
で紐づくメールアドレスを取ってこなかったとすると、
userRepository.save(user)
の際に元々存在していたメールアドレスを消すことになってしまう。
思ったこと
repositoryは集約単位で作る。
その場合は、必ず生成と保存を集約単位で行う必要がある。