I'm one of many trying to understand the concept of aggregate roots, and I think that I've got it! However, when I started modeling this sample project, I quickly ran into a dilemma.
I have the two entities ProcessType
and Process
. A Process
cannot exist without a ProcessType
, and a ProcessType
has many Process
es. So a process holds a reference to a type, and cannot exist without it.
So should ProcessType
be an aggregate root? New processes would be created by calling processType.AddProcess(new Process())
;
However, I have other entities that only holds a reference to the Process
, and accesses its type through Process.Type
. In this case it makes no sense going through ProcessType
first.
But AFAIK entities outside the aggregate are only allowed to hold references to the root of the aggregate, and not entities inside the aggregate. So do I have two aggregates here, each with their own repository?
I largely agree with what Sisyphus has said, particularly the bit about not constricting yourself to the 'rules' of DDD that may lead to a pretty illogical solution.
In terms of your problem, I have come across the situation many times, and I would term 'ProcessType' as a lookup. Lookups are objects that 'define', and have no references to other entities; in DDD terminology, they are value objects. Other examples of what I would term a lookup may be a team member's 'RoleType', which could be a tester, developer, project manager for example. Even a person's 'Title' I would define as a lookup - Mr, Miss, Mrs, Dr.
I would model your process aggregate as:
public class Process
{
public ProcessType { get; }
}
As you say, these type of objects typically need to populate dropdowns in the UI and therefore need their own data access mechanism. However, I have personally NOT created 'repositories' as such for them, but rather a 'LookupService'. This for me retains the elegance of DDD by keeping 'repositories' strictly for aggregate roots.
Here is an example of a command handler on my app server and how I have implemented this:
Team Member Aggregate:
public class TeamMember : Person
{
public Guid TeamMemberID
{
get { return _teamMemberID; }
}
public TeamMemberRoleType RoleType
{
get { return _roleType; }
}
public IEnumerable<AvailabilityPeriod> Availability
{
get { return _availability.AsReadOnly(); }
}
}
Command Handler:
public void CreateTeamMember(CreateTeamMemberCommand command)
{
TeamMemberRoleType role = _lookupService.GetLookupItem<TeamMemberRoleType>(command.RoleTypeID);
TeamMember member = TeamMemberFactory.CreateTeamMember(command.TeamMemberID,
role,
command.DateOfBirth,
command.FirstName,
command.Surname);
using (IUnitOfWork unitOfWork = UnitOfWorkFactory.CreateUnitOfWork())
_teamMemberRepository.Save(member);
}
The client can also make use of the LookupService to populate dropdown's etc:
ILookup<TeamMemberRoleType> roles = _lookupService.GetLookup<TeamMemberRoleType>();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With