Modeling invariants that requires data from multiple aggregates
I want to model the organizations and users using DDD. I have the following aggregate roots: Users Can join multiple organizations Can join at most 100 organizations Can only be deleted when it is not an owner of an organization Organization Can have owners and members Must contain at least 1 owner A user cannot remove himself from an organization When a user creates an organization, he is automatically made the owner Can be deleted under any circumstances At the moment, I have landed on the following (in pseudo code) type User list Organizations list OwnedOrganizations createOrganization(id, name) Organization - Add id to OwnedOrganization and Organizations - Return Organization aggregate func joinOrganization(Organization) - fails if user is already in 100 organizations func delete() - fails if OwnedOrganizations is not empty type Organization func delete() - deletes organization The problem is dealing with the invariants that the last owner cannot be removed from or leave an organization, in otherwords an organization cannot have 0 owners. If we add removeOwner() from the Organization aggregate, we can easily check that the cannot have 0 owners in an Organization invariant holds, but how do we remove the organization from the User's OwnedOrganizations? If we add leaveOrganization() to User, it will not be possible to enforce the invariant that organizations must not have 0 owners. I considered adding removeOwner() to Organization and have it publish an OwnerRemoved event, which will then be picked up by an handler that then: Loads the User Call some method to remove the organization from its internal list The problem with this approach is that we will also have a leaveOrganization() method on User's public API, so now we have user.leaveOrganization() and organization.removeOnwer(), which is frankly quite confusing. Is there something I am missing? Perhaps a separate aggregate?

I want to model the organizations and users using DDD. I have the following aggregate roots:
Users
- Can join multiple organizations
- Can join at most 100 organizations
- Can only be deleted when it is not an owner of an organization
Organization
- Can have owners and members
- Must contain at least 1 owner
- A user cannot remove himself from an organization
- When a user creates an organization, he is automatically made the owner
- Can be deleted under any circumstances
At the moment, I have landed on the following (in pseudo code)
type User
list Organizations
list OwnedOrganizations
createOrganization(id, name) Organization
- Add id to OwnedOrganization and Organizations
- Return Organization aggregate
func joinOrganization(Organization)
- fails if user is already in 100 organizations
func delete()
- fails if OwnedOrganizations is not empty
type Organization
func delete()
- deletes organization
The problem is dealing with the invariants that the last owner cannot be removed from or leave an organization, in otherwords an organization cannot have 0 owners.
If we add removeOwner()
from the Organization aggregate, we can easily check that the cannot have 0 owners in an Organization
invariant holds, but how do we remove the organization from the User
's OwnedOrganizations
?
If we add leaveOrganization()
to User
, it will not be possible to enforce the invariant that organizations must not have 0 owners
.
I considered adding removeOwner()
to Organization
and have it publish an OwnerRemoved
event, which will then be picked up by an handler that then:
- Loads the
User
- Call some method to remove the organization from its internal list
The problem with this approach is that we will also have a leaveOrganization()
method on User
's public API, so now we have user.leaveOrganization()
and organization.removeOnwer()
, which is frankly quite confusing.
Is there something I am missing? Perhaps a separate aggregate?