All fine, except how to make the last rule (When there is no more User in the UserGroup, the UserGroup should be deleted from the views.) work with this model?
It's a constraint on the query executed by the read model to create the view.
This may mean that you need to update the read model to support the constraint. For instance, in your original implementation, you might have have supported the view of user groups with a Set, where each group would be inserted into the set when it was created.
With the new requirement, the read model now needs to track total membership. So you replace the Set with a Map, with you used to track the count of users added and removed from the group. When you need a view of groups, you enumerate the entries in the hash to find the groups that satisfy the constraint (subscribedMembers > unsubscribedMembers).
Note that these details live purely in the read model -- the requirement as described didn't impose any restrictions on the write model. Based on your comment about UserGroupMemberLeft
, it looks like you are trying to introduce end of life to the model.
As pointed out by @JDT, some aggregate needs to be responsible for the end of life. What state determines if the delete of a group is allowed? The aggregate that controls that state is the one that should be responsible for the end of life operation, since it's the only one that knows if the retirement of the group is allowed.
I don't know your domain, but I'd guess that the group itself contains the required state. For instance, if you aren't allowed to retire a group more than once, then it's probably the group that is tracking that. Similarly, if you are only allowed to retire empty groups, its probably the state of the group that is tracking that.
So I assume the group is responsible for its own retirement. At this point, you have two options available to you. One is a process manager, which observes the membership counts and sends a command to retire the group when it observes that there are no members. Because of eventual consistency, this command might be rejected -- for instance, if a new member joins the group after the last one has left.
If you need to retire the group in a transactionally consistent way, then the group locks itself against further enrollment as part of the transaction when the last member leaves. For example, if the write model is broadcasting events to the read model, then the departure of the last user would generate two events, one describing the user leaving, and a second describing the retirement of the group.
Since the group locks itself when the last member leaves, you don't need to worry about the race condition.
Done this way around, your read model can use the Set implementation from earlier, adding groups to the set when they are first created, and removing them when the group is retired.
UserGroupMember
an AR of its own? It can exist as a table in the database, but only because that is how databases store the many-many relation betweenUser
andUserGroup
.