Entity Best Practices & Conventions
This document offers best practices for implementing Aggregate Root and Entity classes in your modules and applications based on Domain-Driven-Design principles.
Ensure you've read the Entities document first.
Entities
Every aggregate root is also an entity. So, these rules are valid for aggregate roots too unless aggregate root rules override them.
- Do define entities in the domain layer.
Primary Constructor
- Do define a primary constructor that ensures the validity of the entity on creation. Primary constructors are used to create a new instance of the entity by the application code.
- Do define primary constructor as public,internalorprotected internalbased on the requirements. If it's not public, the entity is expected to be created by a domain service.
- Do always initialize sub collections in the primary constructor.
- Do not generate Guidkeys inside the constructor. Get it as a parameter, so the calling code will useIGuidGeneratorto generate a newGuidvalue.
Parameterless Constructor
- Do always define a protectedparameterless constructor to be compatible with ORMs.
References
- Do always reference to other aggregate roots by Id. Never add navigation properties to other aggregate roots.
Other Class Members
- Do always define properties and methods as virtual(exceptprivatemethods, obviously). Because some ORMs and dynamic proxy tools require it.
- Do keep the entity as always valid and consistent within its own boundary.
- Do define properties with private,protected,internalorprotected internalsetter where it is needed to protect the entity consistency and validity.
- Do define public,internalorprotected internal(virtual) methods to change the properties (with non-public setters) if necessary.
- Do return the entity object (this) from the setter methods.
 
- Do define properties with 
Aggregate Roots
Primary Keys
- Do always use a Id property for the aggregate root key.
- Do not use composite keys for aggregate roots.
- Do use Guid as the primary key of all aggregate roots.
Base Class
- Do inherit from the AggregateRoot<TKey>or one of the audited classes (CreationAuditedAggregateRoot<TKey>,AuditedAggregateRoot<TKey>orFullAuditedAggregateRoot<TKey>) based on requirements.
Aggregate Boundary
- Do keep aggregates as small as possible. Most of the aggregates will only have primitive properties and will not have sub collections. Consider these as design decisions:
- Performance & memory cost of loading & saving aggregates (keep in mind that an aggregate is normally loaded & saved as a single unit). Larger aggregates will consume more CPU & memory.
- Consistency & validity boundary.
 
Example
Aggregate Root
public class Issue : FullAuditedAggregateRoot<Guid> //Using Guid as the key/identifier
{
    public virtual string Title { get; private set; } //Changed using the SetTitle() method
    public virtual string Text { get; set; } //Can be directly changed. null values are allowed
    public virtual Guid? MilestoneId { get; set; } //Reference to another aggregate root
    public virtual bool IsClosed { get; private set; }
    public virtual IssueCloseReason? CloseReason { get; private set; } //Just an enum type
    public virtual Collection<IssueLabel> Labels { get; protected set; } //Sub collection
    protected Issue()
    {
        /* This constructor is for ORMs to be used while getting the entity from database.
         * - No need to initialize the Labels collection
             since it will be overrided from the database.
           - It's protected since proxying and deserialization tools
             may not work with private constructors.
         */
    }
    //Primary constructor
    public Issue(
        Guid id, //Get Guid value from the calling code
        [NotNull] string title, //Indicate that the title can not be null.
        string text = null,
        Guid? milestoneId = null) //Optional argument
    {
        Id = id;
        Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //Validate
        Text = text;
        MilestoneId = milestoneId;
        
        Labels = new Collection<IssueLabel>(); //Always initialize the collection
    }
    public virtual Issue SetTitle([NotNull] string title)
    {
        Title = Check.NotNullOrWhiteSpace(title, nameof(title)); //Validate
        return this;
    }
    
    /* AddLabel & RemoveLabel methods manages the Labels collection
     * in a safe way (prevents adding the same label twice) */
    public virtual Issue AddLabel(Guid labelId)
    {
        if (Labels.Any(l => l.LabelId == labelId))
        {
            return;
        }
        Labels.Add(new IssueLabel(Id, labelId));
        return this;
    }
    
    public virtual Issue RemoveLabel(Guid labelId)
    {
        Labels.RemoveAll(l => l.LabelId == labelId);
        return this;
    }
    /* Close & ReOpen methods protect the consistency
     * of the IsClosed and the CloseReason properties. */
    
    public virtual void Close(IssueCloseReason reason)
    {
        IsClosed = true;
        CloseReason = reason;
    }
    public virtual void ReOpen()
    {
        IsClosed = false;
        CloseReason = null;
    }
}
Entity
public class IssueLabel : Entity
{
    public virtual Guid IssueId { get; private set; }
    public virtual Guid LabelId { get; private set; }
    protected IssueLabel()
    {
        
    }
    public IssueLabel(Guid issueId, Guid labelId)
    {
        IssueId = issueId;
        LabelId = labelId;
    }
}
References
- Effective Aggregate Design by Vaughn Vernon http://dddcommunity.org/library/vernon_2011
 
                                             
                                    