Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle concurrent constraints across aggregate roots

I'm afraid I already know the answer, but I'm hoping that somebody can provide an alternative solution that haven't found before. As always doing DDD according to Effective Aggregate Design is more difficult than I thought, but here's my scenario.

  • We have two ARs, User and RoleGroup
  • A User can be granted a particular RoleGroup and thereby obtains the permissions provided by the Roles (a collection value object) in that role group. The identity of the role group is kept in the User AR as another VA.
  • When a RoleGroup is removed from the system, we raise a domain event that a handler uses to find all users referring to that RoleGroup and to remove the reference. The corresponding projection denormalizer will use that same event to update the effective roles of the User. This is a combination of the individual roles granted to that User and the roles of all granted RoleGroups.
  • This doesn't have to be transactional (iow it can be eventually consistent).
  • We use Event Sourcing using Jonathan Oliver's EventStore 3.0 and elements from Lokad.CQRS and NCQRS.

So, in theory, when one request (it's an ASP.NET MVC app) is executing the scenario mentioned above, it is possible that another request is granting that same RoleGroup to a User. If that happens just after the above mentioned domain event handler scans for users related to that RoleGroup, that request will complete. At that point you have a RoleGroup that is deleted (albeit not physically) and a User that is still holding the identity of that RoleGroup.

How do you prevent this? We're currently looking at making the identity of the Users granted a particular RoleGroup part of that RoleGroup AR, so that deleting a RoleGroup and granting it to a user will cause a optimistic concurrency conflict. But somehow, this doesn't feel like the correct solution.

like image 562
Dennis Doomen Avatar asked Nov 29 '12 12:11

Dennis Doomen


2 Answers

This is similar to how uniqueness constraints CAN be solved.

Suppose there's a projection with both rolegroups and users that has SERIAL behavior. When rolegroups get archived (i.e. they can no longer be used), the reactive bits sitting on top of the projection can notify all the users that have been granted said rolegroup that they are no longer part of it. When concurrently this archived rolegroup is granted to a user (or a set of), the serial nature of the projection can be leveraged to tell this user too that they are no longer part of the group.

All that said, this is just housekeeping. It's only when the rolegroups and users get used that a correct view is important. Since I presume rolegroups will carry an IsArchived bit, I can safely filter them out at that time, without worrying about some dangling edge-case for which we still have to prove that it has to be resolved in an automated way.

As an aside, scanning the event log would also reveal this situation, i.e. are there any users granted a rolegroup that was archived before that point in time (or around that point in time)? An admin could resolve this by issueing a compensating command to the user aggregate.

"It depends" TM

Edit: I've given a technical solution to this problem. I would encourage other readers to explore different ways of modeling & solving these kinds of problems. Sometimes, perhaps even most of the times, the answer isn't technical at all. YMMV.

like image 101
Yves Reynhout Avatar answered Nov 01 '22 12:11

Yves Reynhout


Why are you adding a RoleGroup reference to the User aggregate? Are there any invariants on the User that use this information?

I imagine this can be modelled much simpler by granting the RoleGroup to the User via the RoleGroup aggregate, emitting something like a RoleGroupGrantedToUser event. When a RoleGroup is removed it emits a RoleGroupRemoved event. After this event the RoleGroup no longer accepts new Users.

like image 33
Martijn van den Broek Avatar answered Nov 01 '22 11:11

Martijn van den Broek