1

I have a class which is an aggregate root and represents a Person. A person must have a Title ( Mr, Mrs, Ms etc ) which is a property of the Person object. When creating a person the user must select a title from a drop down list the contents of which are administered via another page.

There is also the option to view this information via a read-only page so by using the model below I have the Id and Name "to hand" and do not have to go away and retrieve the ValueName of the title based on the title id which is what I would have to do if I were to add TitleId as a property of Person rather than Title.

When saving a person object the Id of the title is persisted as part of the person and is stored in the Person db table (the db table containing the titles is not touched)

When populating person the stored procedure joins person on to title based on title id and returns information used to populate the person an title classes.

public class Person : IAggregateRoot, IPerson 
{
   public string Forename { get;set; }
   public string Surname{ get;set; }
   public ITitle Title { get;set; }
}

public class Title : IAggregateRoot , ITitle
{
    public Guid Id {get;set;}
    public string ValueName {get;set;}
}

My question is: From a DDD perspective is it ok to use this class structure and have an aggregate root object nested within another aggregate root object given that it is a "fixed list" or "lookup value" and also needs to be maintained separately by an administrator?

Anthony Walters
  • 143
  • 1
  • 12

2 Answers2

1

Aggregate roots are the "ROOT" of the aggregate by definition so the short answer to your question is no, aggregate roots cannot be nested within another aggregate root.

Keep in mind that the aggregate root is all depending upon context. Therefore, in the context of managing a "Person", Title is just an entity or value object. In the context of managing a "Title", Title may be the aggregate root. If this is the case, there should be two separate "Title" classes, one for each context. This is a change management strategy so that changes made in one context do not affect the other context.

Here is an example that might help shed some light on the subject. Please forgive the names as more knowledge of your domain would be required to make more meaningful names so I'm going to make some assumptions for the purpose of this example:

Context of Hiring a Person

public class Person : IAggregateRoot, IPerson 
{
   public string Forename { get;set; }
   public string Surname{ get;set; }
   public Title Title { get;set; }
   public DateTime? HireDate { get; set; }
   public void Hire(Title granted, IPersonRepository repository)
   {
       Title = granted;  //Grant the new hire this title that we have at our company
       HireDate = DateTime.Now;
       repository.Save(this);
   }
}

public struct Title /* I like to make my value objects structs */
{
    public Title (Guid id, string value)
    {
        Id = id;
        Value = value;
    }
    public Guid Id { get; private set; }
    public string FullTitle{ get; private set; }  /* This would be the prefix + value + level when loaded from the repository because, in this context, we don't have any need for that level of separation. */
}

Context of creating a new title with different levels

public class Title : IAggregateRoot, ITitle
{
    public string Prefix { get; set; }
    public string Value { get; set; }
    public int Level { get; private set; }
    public void Create(int levelsAvailable, ITitleRepository repository)
    {
        for (int i = 1; i <= levelsAvailable; i++)
        {
            Title title = new Title(Prefix, Value) { Level = i };
            repository.Save(title);
        }
    }
}

public class Person : IEntityObject, IPerson 
{
   public ITitle Title { get;set; }
   public string Name { get; set; }
}

To illuminate the reason for the change management strategy, consider if the business comes back and says that every new hire is to be granted a probationary title for a period of 90 days and that probationary titles are defined as level 1 titles. Using this strategy above, only one context would need to be changed which eliminates any possible unknown failures for the other context.

Hope this helps!

Aaron Hawkins
  • 2,611
  • 1
  • 20
  • 24
  • Value objects - by definition - do not have IDs. – Matt Johnson-Pint Jun 20 '14 at 21:50
  • @MattJohnson You are correct. I mainly put it in there because that's the way the OP originally had it. In addition, many times it is easier and you get better performance if you store the database ID of an object along with it so you don't end up having to query the Title ID based on the value before saving the changes made to a person. Personally, when these situations arise, I use the type "object" to store my IDs and I make them private. This allows my domain code to remain repository agnostic while ensuring that application developers don't write code against the database ID. – Aaron Hawkins Jun 23 '14 at 16:22
0

No, it's not okay to nest aggregate roots. An aggregate can contain multiple entities, or references (by id) to another aggregate, but it cannot encapsulate an aggregate.

I would also say that "Title" is not a good candidate for an entity (aggregate or otherwise). It should be considered a "value object", and really - it should just be a string. You may have a separate lookup table of titles, but that doesn't mean you should reference that table from your Person object.

Separate the process of maintaining the titles lookup table from the assignment of title to a person. When a title is chosen, copy the string to the person object. Denormalization is the way to go with value objects.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • Just a thought: "Title" it should be just a string only if _any_ string can be a Title. – MikeSW Jun 21 '14 at 13:57
  • Thanks Matt, Im now thinking a Person class should contain a titleId and not a title object. With regards to maintaining the title list does the fact that the title objects need to be persisted in their own right not imply that a title should be an aggregate root? (currently my repositories only allow objects that implement IAggregateRoot to be persisted) – Anthony Walters Jun 21 '14 at 16:04
  • @MikeSW - not necessarily. Input validation can be part of the process of assigning a title. – Matt Johnson-Pint Jun 21 '14 at 18:32
  • @AnthonyWalters - That would *work*, but it might not offer the best maintainability. Consider that you might want to remove titles at some point. If you reference by TitleID, then you can't ever delete a title. You could "soft delete" by marking a flag in the Titles class, but then you have to remember to check it. – Matt Johnson-Pint Jun 21 '14 at 19:15
  • Also, if you reference by ID, then you'd also need a unique constraint over the titles. Otherwise, two people with the same title might have different TitleIDs. – Matt Johnson-Pint Jun 21 '14 at 19:18
  • Ultimately, it comes down to the difference between an entity and a value object, which is identity and interchangeability. If two people have the same title, then those titles are interchangeable and don't require identity. – Matt Johnson-Pint Jun 21 '14 at 19:19
  • Now perhaps there's more to consider here than just Title. Does your domain require the concept of a "Position"? If so, then perhaps a Person has a PositionID, and a Position has a Title and list of JobFunctions, and who knows what else. Since one position isn't interchangeable with another, that's a good candidate for an entity. – Matt Johnson-Pint Jun 21 '14 at 19:21