Designing an Aggregate and choosing an Aggregate Root getting tricky for me all the time especially when it comes to ensuring right transactional contexts and consistency constraints so I'm wondering whether there are any practices that can make it easier.
There is a student attendance tracking system that keeps records of student attendances of groups.
It seemed a good idea to design Aggregate boundaries to match the taxonomy above exactly, where the main pillar was the Course Aggregate Root:
It is nice because:
But at the same time, it's very fat, hard to maintain and designed with false invariants in mind that caused transactional failures: nothing about creating a new Meeting item should logically interfere with editing/creating a course for example. It just doesn't scale. Moreover, usually, you may want to update Group by adding some Sessions having GroupId and it should not require fetching the whole Course.
After all, I have not problems with transactions, but, unfortunately, it added more questions than solved problems:
Meeting#attend(UserId userId)method, how to ensure the user is eligible (signed up for the Group) for attending this meeting?
I'm thinking about moving Meeting inside the Group AR, where I can put
Group#attend(UserId userId) method. But there is still a problem of ensuring the Group is created inside the Course only.
I was thinking about hiding (package private'ing/protected'ing) Group constructor and adding
Course#createGroup(GroupParams p): Group but I'm not sure if it's valid to mess two different concepts (Group and Course aggregate roots) in each other.
Moreover, it doesn't solve the problem users still can remove Group via its repository (a Spring's
Repository#delete(Group) in my case). I possibly can solve this by:
GroupRemovedEventso the Course can subscribe to it and modify its
get all groups of the course by CourseId givenis a day-to-day operation.
I would appreciate any ideas on this matter. Thank you.