7

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 Processes. 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?

acdcjunior
  • 132,397
  • 37
  • 331
  • 304
Vern
  • 233
  • 3
  • 13

3 Answers3

15

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>();
David Masters
  • 8,069
  • 2
  • 44
  • 75
  • Very interesting approach David. Thank you for sharing that! How did you implement the LookupService? As a infrastructure service with a generic Get method that uses the underlying ORM framework directly without any repositories? Can someone else comment on this approach? Did you do something similar, or did you solve this problem a different way? – Vern Feb 08 '11 at 11:24
  • 2
    I have ILookupService defined in my infrastructure, then an implementation in a seperate project which is loaded up using DI (unity). The implementation is raw SQL/Sprocs, but of course you could easily make use of an ORM. This service can be used by the domain and by the query services to provide ViewModels to the UI. The reason I use generics is because all 'LookupItems' simply have an ID, Description, and list of attributes. This means I can use a generic means of storing the data for all lookups/lookup items by using their type name. I.E. I wouldn't have a table called 'ProcessType'. – David Masters Feb 08 '11 at 11:49
  • I see. I just tried implementing it and it seems like an elegant solution. For now I'll stick to this approach unless someone comes by and tells us they have a better solution :-) I'll let the question stay open for a few days to get more opinions on this topic. – Vern Feb 08 '11 at 12:21
  • 1
    I both agree and respectfully disagree. I too use a lookup service to encapsulate many simple items such as title that are essential to the app but inconsequential in terms of concept. OTOH Knowledge Layer objects are often value objects but a significant feature of the model because they are major definitions. If ProcessType is a significant feature of your model, and I think it probably is, give it a prominent repository/service of it's own. Don't bury an important defining aspect of your model in a generic lookup solely from misconception that value objects do not have repositories. – Sisyphus Feb 09 '11 at 00:46
  • @Sisyphus, indeed my solution would only make sense if 'ProcessType' is a simple 'marker' value object that simply has an ID, description and some attributes (like process.ProcessType.IsSomething) and from reading his question this is all it seems to be. I'm not suggesting all value objects should use a generic lookup service, just these types of objects that are simply used to group aggregates from a query perspective and possibly provide configuration though their attributes. – David Masters Feb 09 '11 at 09:08
  • @Vern - did this work out for you? I've noticed that you've asked 3 questions but not accepted any of the answers :P – David Masters Feb 16 '11 at 08:52
12

Not so simple. ProcessType is most likley a knowledge layer object - it defines a certain process. Process on the other hand is an instance of a process that is ProcessType. You probably really don't need or want the bidirectional relationship. Process is probably not a logical child of a ProcessType. They typically belong to something else, like a Product, or Factory or Sequence.

Also by definition when you delete an aggregate root you delete all members of the aggregate. When you delete a Process I seriously doubt you really want to delete ProcessType. If you deleted ProcessType you might want to delete all Processes of that type, but that relationship is already not ideal and chances are you will not be deleting definition objects ever as soon as you have a historical Process that is defined by ProcessType.

I would remove the Processes collection from ProcessType and find a more suitable parent if one exists. I would keep the ProcessType as a member of Process since it probably defines Process. Operational layer (Process) and Knowledge Layer(ProcessType) objects rarely work as a single aggregate so I would have either Process be an aggregate root or possibly find an aggregate root that is a parent for process. Then ProcessType would be a external class. Process.Type is most likely redundant since you already have Process.ProcessType. Just get rid of that.

I have a similar model for healthcare. There is Procedure (Operational layer) and ProcedureType (knowledge layer). ProcedureType is a standalone class. Procedure is a child of a third object Encounter. Encounter is the aggregate root for Procedure. Procedure has a reference to ProcedureType but it is one way. ProcedureType is a definition object it does not contain a Procedures collection.

EDIT (because comments are so limited)

One thing to keep in mind through all of this. Many are DDD purists and adamant about rules. However if you read Evans carefully he constantly raises the possibility that tradeoffs are often required. He also goes to pretty great lengths to characterize logical and carefully thought out design decisions versus things like teams that do not understand the objectives or circumvent things like aggregates for the sake of convenience.

The important things is to understand and apply the concepts as opposed to the rules. I see many DDD that shoehorn an application into illogical and confusing aggregates etc for no other reason than because a literal rule about repositories or traversal is being applied, That is not the intent of DDD but it is often the product of the overly dogmatic approach many take.

So what are the key concepts here:

Aggregates provide a means to make a complex system more manageable by reducing the behaviors of many objects into higher level behaviors of the key players.

Aggregates provide a means to ensure that objects are created in a logical and always valid condition that also preserves a logical unit of work across updates and deletes.

Let's consider the last point. In many conventional applications someone creates a set of objects that are not fully populated because they only need to update or use a few properties. The next developer comes along and he needs these objects too, and someone has already made a set somewhere in the neighborhood fora different purpose. Now this developer decides to just use those, but he then discovers they don't have all the properties he needs. So he adds another query and fills out a few more properties. Eventually because the team does not adhere to OOP because they take the common attitude that OOP is "inefficient and impractical for the real world and causes performance issues such as creating full objects to update a single property". What they end up with is an application full of embedded SQL code and objects that essentially randomly materialize anywhere. Even worse these objects are bastardized invalid proxies. A Process appears to be a Process but it is not, it is partially populated in different ways any given point depending on what was needed. You end up with a ball mud of numerous queries to continuously partially populate objects to varying degrees and often a lot of extraneous crap like null checks that should not exist but are required because the object is never truly valid etc.

Aggregate rules prevent this by ensuring objects are created only at certain logical points and always with a full set of valid relationships and conditions. So now that we fully understand exactly what aggregate rules are for and what they protect us from, we also want to understand that we also do not want to misuse these rules and create strange aggregates that do not reflect what our application is really about simply because these aggregate rules exists and must be followed at all times.

So when Evans says create Repositories only for aggregates he is saying create aggregates in a valid state and keep them that way instead of bypassing the aggregate for internal objects directly. You have a Process as a root aggregate so you create a repository. ProcessType is not part of that aggregate. What do you do? Well if an object is by itself and it is an entity, it is an aggregate of 1. You create a repository for it.

Now the purist will come along and say you should not have that repository because ProcessType is a value object, not an entity. Therefore ProcessType is not an aggregate at all, and therefore you do not create a repository for it. So what do you do? What you don't do is shoehorn ProcessType into some kind of artificial model for no other reason than you need to get it so you need a repository but to have a repository you have to have an entity as an aggregate root. What you do is carefully consider the concepts. If someone tells you that repository is wrong, but you know that you need it and whatever they may say it is, your repository system is valid and preserves the key concepts, you keep the repository as is instead of warping your model to satisfy dogma.

Now in this case assuming I am correct about what ProcessType is, as the other commentor noted it is in fact a Value Object. You say it cannot be a Value Object. That could be for several reasons. Maybe you say that because you use NHibernate for example, but the NHibernate model for implementing value objects in the same table as another object does not work. So your ProcessType requires an identity column and field. Often because of database considerations the only practical implementation is to have value objects with ids in their own table. Or maybe you say that because each Process points to a single ProcessType by reference.

It does not matter. It is a value Object because of the concept. If you have 10 Process objects that are of the same ProcessType you have 10 Process.ProcessType members and values. Whether each Process.ProcessType points to a single reference, or each got a copy, they should still by definition all be exactly the same things and all be completely interchangeable with any of the other 10. THAT is what makes it a value Object. The person who says "It has an Id therefore is cannot be a value Object you have an entity" is making a dogmatic error. Don't make the same error, if you need an ID field give it one, but don't say "it can't be a Value Object" when it in fact is albeit one that for other reason you had to give an Id to.

So how do you get this one right and wrong? ProcessType is a Value Object, but for some reason you need it to have an Id. The Id per se does not violate the rules. You get it right by having 10 processes that all have a ProcessType that is exactly the same. Maybe each has a local deeep copy, maybe they all point to one object. but each is identical either way, ergo each has an Id = 2, for example. You get is wrong when you do this: 10 Processes each have a ProcessType, and this ProcessType is identical and completely interchangeable EXCEPT now each also has it's own unique Id as well. Now you have 10 instances of the same thing but they vary only in Id, and will always vary only in Id. Now you no longer have a Value Object, not because you gave it an Id, but because you gave it an Id with an implementation that reflects the nature of an entity - each instance is unique and different

Make sense?

Sisyphus
  • 4,181
  • 1
  • 22
  • 15
  • Thank you very much - you are absolutely right. But according to Evans, in his blue book, he says that you create repositories for aggregates. However, in my scenario, I need a repository for the standalone class (ProcessType), because I need to be able to create new process types. Not that it would happend very often, but it is an option. How would you handle that? – Vern Feb 07 '11 at 16:23
  • By create, I mean persist the object in the database - for which I use the repository pattern. The repository is not responsible for creating the objects. – Vern Feb 07 '11 at 18:33
  • When using a Knowledge Layer based design consider if the knowledge creation itself should or should not be part if the primary app. Depends. In my case there are millions of procedures and symptoms. Need one not in system? There's an app for that. And one senior physician who gets to use it. So we have a new patient who has flesh eating dingle berries, 1 of 10 known cases. The man at the top is gonna say there's a write in for that. The doctors piss and moan but given their way would make their own system unusable in a month. Different concern gets a different app not part of primary. – Sisyphus Feb 07 '11 at 19:32
  • That's a very interesting point. I think that I will try that strategy for now. But what if new types are added more often? Another example could be if you have a range of products, that you group together by having a ProductGroup entity with a one-to-many to Products. Where would you put the ProductGroup? It's the same case. Product has to be in a group, but other entities can hold a reference directly to a product. However, in this case it is normal to remove and add new groups. How would you handle that? – Vern Feb 07 '11 at 20:24
  • Just read the edit. Your point about VOs makes sense, and maybe my ProcessType is a VO. But lets take the classic example. In my GUI, when I'm creating a new Process, I need to select a ProcessType from a dropdown list. So I need a method to in a repository to fetch all ProcessTypes from my database, `ProcessTypeRepository.GetAllProcessTypes()`. If a type does not fit my process, I should be able to create a new ProcessType, `ProcessTypeRepository.AddProcessType(ProcessType pt)`. All those methods, to me, indicates that this is not a VO. But maybe I'm wrong? Continues in the following comment. – Vern Feb 07 '11 at 20:56
  • The same discussion goes on [here](http://www.lostechies.com/blogs/joe_ocampo/archive/2007/04/23/a-discussion-on-domain-driven-design-value-objects.aspx), in the comments. See the comment written by Aaron on 04-25-2007 5:11 PM. Unfortunately the author never answers it. – Vern Feb 07 '11 at 20:57
  • That blog is misleading and takes you down a path where anything with a name is an entity. Identity in terms of value vs entity is instance identity. If for a process you need a specific type of ProcessType, and there may be new ones in the future, it is still a value object. If ProcessType is an entity that implies when you get a Process you must also get a specific instance of ProcessType that was created for that Process, and that your equivalence of Process.Type and ProcessType is invalid. Reread Evans pg 150 on this. He says "usually" and "often" I beg to differ even with that. – Sisyphus Feb 09 '11 at 00:36
0

Look i think you have to restructure your model. Use ProcessType like a Value Object and Process Agg Root. This way Every Process has a processType

Public class Process
{
      Public Process()
      {

      }

      public ProcessType { get; }

}

for this u just need 1 agg root not 2.

Pedro de la Cruz
  • 343
  • 1
  • 5
  • 15