DDD: cumulative roots

I need help finding my total root and boundary.

I have 3 objects: Plan, PlannedRole and PlannedTraining. Each plan can include many planned roles and scheduled broadcasts.

Solution 1. At first, I thought that the plan is the combined root, because PlannedRole and PlannedTraining do not make sense from the context of the Plan. They are always within the plan. In addition, we have a business rule that states that each plan can have no more than 3 planned roles and 5 scheduled broadcasts. Therefore, I thought, having assigned the Plan as the aggregate root, I can apply this invariant.

However, we do have a search page where the user searches for Plans. The results show several properties of the Plan itself (and not one of its Planned Roles or Planned Trains). I thought that if I had to load the entire unit, it would have a lot of overhead. There are about 3,000 plans, and each can have several children. Uploading all of these objects together and then ignoring PlannedRoles and PlannedTrainings on the search page makes no sense to me.

Solution 2. I just realized that the user wants 2 more search pages where they can search for planned roles or planned trainings. This made me realize that they were trying to access these objects on their own and out of the context of the Plan. Therefore, I thought that I was mistaken in my original design, and that is how I came up with this solution. So, I thought that we have 3 units, 1 for each entity.

This approach allows me to independently search for each Entity, as well as eliminate the performance problem in solution 1. However, using this approach, I can not ensure compliance with the previously established invariant.

There is also another invariant that states that a plan can only be changed if it has a certain status. Thus, I could not add any PlannedRoles or PlannedTrainings to a plan that is not in this status. Again, I cannot force this invariant to a second approach.

Any advice would be greatly appreciated.

Cheers, Mosh

+6
aggregate domain-driven-design aggregateroot aggregates
source share
3 answers

I had similar problems with this when developing my model and asked this question, which, I think, could help you, especially regarding your first moment.

DDD - How to implement high-performance repositories for search .

When it comes to searching, I don’t work with a “model”, instead I have specialized search repositories that return “Consolidated” objects ... i.e. "PlanSummary". These are nothing more than information objects (they can be considered more likely as reporting) and are not used in a transactional sense - I do not even define them in my library of model classes. By creating these dedicated repositories and types, I can implement high-performance search queries that can contain grouped data (for example, PlannedTraining counts) without loading all the associations of the population in memory. When the user selects one of these summary objects in the user interface, I can then use the identifier to retrieve the actual model object and perform transactional operations and commit the changes.

So, for your situation, I would provide these specialized search repositories for all three entities, and when the user wants to perform actions against one, you always extract the aggregate of the plan to which it belongs.

Thus, you perform search queries, while maintaining your only aggregate with the required invariants.

Edit - Example:

OK, so I assume that the implementation is subjective, but this is how I processed it in my application using the TeamMember aggregate as an example. An example is written in C #. I have two class libraries:

  • Model
  • Reports

The Model library contains an aggregate class with all invariants included, and the Reporting library contains this simple class:

public class TeamMemberSummary { public string FirstName { get; set; } public string Surname { get; set; } public DateTime DateOfBirth { get; set; } public bool IsAvailable { get; set; } public string MainProductExpertise { get; set; } public int ExperienceRating { get; set; } } 

The report library also contains the following interface:

 public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary> { } 

This is the interface that the application layer will use (which in my case will be a WCF service) and will allow implementation through my IoC (Unity) container. IReportRepository lives in the Infrastructure.Interface library, as well as the base ReportRepositoryBase. Therefore, I have two different types of repository on my system: aggregated repositories and report repositories ...

Then in another Repositories.Sql library, I have an implementation:

 public class TeamMemberSummaryRepository : ITeamMemberSummaryRepository { public IList<TeamMemberSummary> FindAll<TCriteria>(TCriteria criteria) where TCriteria : ICriteria { //Write SQL code here return new List<TeamMemberSummary>(); } public void Initialise() { } } 

So, in my application level:

  public IList<TeamMemberSummary> FindTeamMembers(TeamMemberCriteria criteria) { ITeamMemberSummaryRepository repository = RepositoryFactory.GetRepository<ITeamMemberSummaryRepository>(); return repository.FindAll(criteria); } 

Then, in the client, the user can select one of these objects and perform an action against one at the application level, for example:

  public void ChangeTeamMembersExperienceRating(Guid teamMemberID, int newExperienceRating) { ITeamMemberRepository repository = RepositoryFactory.GetRepository<ITeamMemberRepository>(); using(IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork()) { TeamMember teamMember = repository.GetByID(teamMemberID); teamMember.ChangeExperienceRating(newExperienceRating); repository.Save(teamMember); } } 
+9
source share

The real problem here is the violation of the PSA. Your application input is in conflict with the output.

Stick to the first solution (Plan == aggregate root). Artificial promotion of objects (or even objects of value) for combining roots distorts the entire domain model and destroys everything.


You might want to check the so-called CQRS (segregation of responsibility for the team), which is ideal for fixing this particular problem. Here is an example application from Mark Nijhof. Here's a nice 'get-started' .

+4
source share

This is the whole point of the CQRS architecture: separating the commands - which change the domain - from the queries - it’s just to give an idea of ​​the state of the domain, because the requirement for commands and queries is so different.

You can find good news on these blogs:

and many other blogs (including mine )

+3
source share

All Articles