0

Relatively new to Domain Driven Design i decided to try it out in an saas app currently under development/refactoring. I've refactored the identity part out to it's own context (class library in .net) and came up with the following Domain models:

  • Organization. Is an Entity and Aggregate Root (all users in the system should be part of an organization) and Users can only by added through the Organization AR.
  • User. Is an Entity referenced (List of User) by Organization (the user that is part of an Organization.

So one of the most important constraints is that a User should always be part of an Organization. At this point there aren't many business rules so the Domain Entities are realy simple at this point. I have multiple designs but cannot figure out what design is best in terms of encapsulation/performance (i know, don't optimize prematurely).

Design 1:

  • Organization implements Users as child entities (object reference). In this way Users can only be added through the Organization AR giving full encapsulation. This design also makes it explicit that Users in this context should always be deleted when an Organization is deleted. Exactly what needs to be done. But now when i want to add a User i have to load the whole Organization AR (keeping invariants) including all Users within the Organization so i can check if a User doesn't exists already within the Organization. I don't see any problems with this approach but for large Organizations that can have hundreds of Users this can be resource intensive. Another approach within this design to ensure uniqueness is to update a read model (UniqueUser) when a User is added to an Organization. When the API tries to re-register a User than in the AfterLoginEnsureOrganization UseCase the User is first validated by the UniqueUser readmodel repository.

Design 2:

  • Organization and User are both AR (User referencing Organization by OrganizationId) and the UseCase implements the constraints leaving us with no encapsulation whatsoever. Currently i'm the only developer for this app so no biggie but how to prevent other developers from adding orphan users by misusing the IUserRepository and not the specific UseCase.

I'm not sure where to make the trade-off between encapsulation, complexity and performance.

1

Relatively new to Domain Driven Design

There's an important message in this video of Greg Young, that you really want to be paying attention to how much competitive advantage you derive from the capabilities you are developing. "Users" and "Organizations" sounds suspiciously like a domain where you could integrate with an off the shelf solution.

when i want to add a User i have to load the whole Organization AR (keeping invariants) including all Users within the Organization so i can check if a User doesn't exists already within the Organization. I don't see any problems with this approach

Concurrent edits of an aggregate are painful - if you put all of the entities into a single aggregate, you get lots of data races. If I'm trying to make a change to Alice, I probably don't care very much about all of the other users in the system.

"Uniqueness" is part of a more general category called Set Validation, and yeah -- if you really do need immediately reject any attempt to modify the model that would violate uniqueness, then you are going to need a check for it. Sometimes that means loading the entire set, other times it means generating a unique membership key, and ensuring that it isn't occupied.

In many cases, it turns out that set validation isn't a hard constraint; in those cases it may be better to allow the constraint to be violated, and raise an exception report when the violation is detected, so that a human being can figure out what to do. (An important consideration: how do you know that the first entry, rather than the second one, is "correct"?)

  • Thanks for your answer and this gives a new direction and another perspective to look at the current design.I think uniqueness at this point isn't a real constraint and certainly doesn't give competitive advantage nor is it a good economical trade-off. I think that validating the constraint at DB level (unique index) is suffcient and let that exception bubble up to the API exception handler. I also believe that my aggregate design was wrong because of the valid point about concurrent edits. Editing a user doesn't mean invariants of other users should be validated – JohnDoe Jan 16 at 9:31
-1

Still having troubles with the design. I removed the Users as child entities from Organization (List of User references) and created OrganizationId property into the User Entity (is now AR). So now i have two Aggregate Roots, namely Organization and User. In the UseCase class i create a Organization and User and in the User creation i supply the OrganizationId. This feels a little bit like an anemic domain design and now i have two AR's but no encapsulation. In the previous design i had the Organization AR that has full encapsulation around Users with the method organization.AddUser(user). The new design from UseCase perspective now looks like this:

var userId = new UserId(message.ForUserId);
var organizationId = new OrganizationId(message.ForOrganization);
var organization =  new Organization(organizationId, message.ForOrganization, string.Empty);

var user = new User(
 organizationId,
 userId, 
 message.ForPersonalName, 
 string.Empty
);

this._organizationRepository.Add(organization);

this._userRepository.Add(user);
await this._userRepository.UnitOfWork.SaveEntitiesAsync();

it doesn't feel right to have two repositories in one transaction. What am i missing here? The UOW is used in both repositories and they share the same Entity Framework DBContext so if one transaction fails they both will be rolled back. So from an invariant point of view the organization and user constraint will remain intact.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.