问题
I need help with finding my aggregate root and boundary.
I have 3 Entities: Plan, PlannedRole and PlannedTraining. Each Plan can include many PlannedRoles and PlannedTrainings.
Solution 1: At first I thought Plan is the aggregate root because PlannedRole and PlannedTraining do not make sense out of the context of a Plan. They are always within a plan. Also, we have a business rule that says each Plan can have a maximum of 3 PlannedRoles and 5 PlannedTrainings. So I thought by nominating the Plan as the aggregate root, I can enforce this invariant.
However, we have a Search page where the user searches for Plans. The results shows a few properties of the Plan itself (and none of its PlannedRoles or PlannedTrainings). I thought if I have to load the entire aggregate, it would have a lot of overhead. There are nearly 3000 plans and each may have a few children. Loading all these objects together and then ignoring PlannedRoles and PlannedTrainings in the search page doesn't make sense to me.
Solution 2: I just realized the user wants 2 more search pages where they can search for Planned Roles or Planned Trainings. That made me realize they are trying to access these objects independently and "out of" the context of Plan. So I thought I was wrong about my initial design and that is how I came up with this solution. So, I thought to have 3 aggregates here, 1 for each Entity.
This approach enables me to search for each Entity independently and also resolves the performance issue in solution 1. However, using this approach I cannot enforce the invariant I mentioned earlier.
There is also another invariant that states a Plan can be changed only if it is of a certain status. So, I shouldn't be able to add any PlannedRoles or PlannedTrainings to a Plan that is not in that status. Again, I can't enforce this invariant with the second approach.
Any advice would be greatly appreciated.
Cheers, Mosh
回答1:
I was having similar problems with this when designing my model and asked this question which I think might help you, especially regarding your first point.
DDD - How to implement high-performing repositories for searching.
When it comes to searching I don't work with the 'model', instead I have specialised search repositories that return 'Summary' objects... i.e. 'PlanSummary'. These are nothing more than information objects (could be thought of more like reporting) and are not used in a transactional sense - I don't even define them in my model class library. By creating these dedicated repositories and types I can implement high performing search queries that can contain grouped data (such as a PlannedTraining count) without loading all of the associations of the aggregate in memory. Once a user selects one of these summary objects in the UI, I can then use the ID to fetch the actual model object and perform transactional operations and commit changes.
So for your situation I would provide these specialised search repositories for all three entities, and when a user wishes to perform and action against one, you always fetch the Plan aggregate that it belongs to.
This way you have the performant searches whilst still maintaining your single aggregate with the required invariants.
Edit - Example:
OK, so I guess implementation is subjective, but this is how I have handled it in my application, using a 'TeamMember' aggregate as an example. Example written in C#. I have two class libraries:
- Model
- Reporting
The Model library contains the aggregate class, with all invariants enforced, 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 Reporting library also contains the following interface:
public interface ITeamMemberSummaryRepository : IReportRepository<TeamMemberSummary>
{
}
This is the interface that the application layer (which in my case happens to be WCF services) will consume and will resolve the implementation via my IoC container (Unity). The IReportRepository lives in an Infrastructure.Interface library, as does a base ReportRepositoryBase. So I have two different types of repository in my system - Aggregate repositories, and reporting repositories...
Then in another library, Repositories.Sql, I have the 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 then, in my application layer:
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 in the application layer, 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);
}
}
回答2:
Real problem here is SRP violation. Your input part of app goes in conflict with output.
Stick with first solution (Plan==aggregate root). Artificially promoting entities (or even value objects) to aggregate roots distorts whole domain model and ruins everything.
You might want to check out so called CQRS (command query responsibility segregation) architecture which would fit perfectly to fix this particular issue. Here's an example app by Mark Nijhof. Here's nice 'getting-started' list.
回答3:
This is the whole point of CQRS architectures: segregate Commands - that modify the domain - from Queries - that simply give a view of domain state, because requirement for Commands and Queries are so different.
you can find a good introductions on these blogs :
- Udi Dahan
- Greg Young
and on many other blogs (including mine)
来源:https://stackoverflow.com/questions/2558469/ddd-aggregate-roots