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?

Apr 11, 2025 - 08:42
 0
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?