If you commonly need to update multiple aggregates, your aggregate boundaries might be wrong.
Of course, there can be scenarios, in which multiple aggregates do need to be updated. A saga is the typical way to deal with this. Note, however, that you are working across consistency boundaries here.
If you have the need to update multiple aggregates atomically, it's very likely that you have to rethink your boundaries.
Your chat application is a perfect example to show how this can be very easy (if possibly counter-intuitive sometimes), as well as quite involved.
Boundaries
Let's say you have a simple requirement: a user cannot be in the same room twice.
One option is to make the Room
your aggragate, to which you can apply commands like JoinUser
, GrantPrivileges
, PostMessage
, etc. The Room
will be responsible for enforcing the invariant.
Another option is to have a Chatter
aggregate (essentially the user). Again, you have commands like JoinRoom
and the Chatter
enforces the invariant.
Which layout you use depends on which other invariants you want to enforce.
The Room
approach allows you specify restrictions between actions in the same room (e.g. no more than 20 users can be in a room).
The Chatter
approach allows you to specify restrictions of your user, possibly regarding multiple rooms (e.g. a user can only be in one room at a time).
Sagas
Where it becomes tricky is if you want some combination of those restrictions. If you can't find a way to slice your aggregates accordingly (without creating some kind of singleton-super-aggregate that encompasses your entire application), you may be able to work around this with a saga.
In this scenario, adding the concept of a Session
might be the right move. After the session is created, your saga tries to register it with the User
and Room
aggregates (and kills the session if either one fails).
I would still spend some time trying to see if there's another (perhaps non-obvious) way to slice the aggregates first. Remember that they don't have to mirror your read-models: if you don't have any consistency constraints that apply to a whole room, you may not need a Room
aggregate on the command side at all.