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?