Domain-Driven Design - Aggregate Roots in C#
Before you look at the Aggregate Roots example in C#, you can see at my post on what Aggregate Roots are in Domain-Driven Design.
Aggregate Root interface in C#
public interface IAggregateRoot : IEntity
{
IReadOnlyList<IDomainEvent> DomainEvents { get; }
void AddDomainEvent(IDomainEvent domainEvent);
void ClearDomainEvents();
}
About this code snippet:
- It inherits from
IEntity
(You can found it in this post: Entities in Domain-Driven Design).
Aggregate Root base class in C#
public abstract class AggregateRoot : Entity, IAggregateRoot
{
private readonly List<IDomainEvent> _domainEvents = new List<IDomainEvent>();
public IReadOnlyList<IDomainEvent> DomainEvents
{
get => this._domainEvents.AsReadOnly();
}
protected AggregateRoot() { }
protected AggregateRoot(Guid id) : base(id) { }
public void AddDomainEvent(IDomainEvent domainEvent)
{
this._domainEvents.Add(domainEvent);
}
public void ClearDomainEvents()
{
this._domainEvents.Clear();
}
}
About this code snippet:
- It is an
abstract
class: This prevents this class from being instantiated and allows to declare abstract methods.
Aggregate Root example in C#
public sealed class User : AggregateRoot
{
public FirstName FirstName { get; private set; }
public LastName LastName { get; private set; }
public BirthDate BirthDate { get; private set; }
private User() { }
private User(Guid id, FirstName firstName, LastName lastName, BirthDate birthDate) : base(id)
{
this.FirstName = firstName;
this.LastName = lastName;
this.BirthDate = birthDate;
}
public static User Create(Guid id, FirstName firstName, LastName lastName, BirthDate birthDate)
{
var user = new User(id, firstName, lastName, birthDate);
user.AddDomainEvent(new UserCreatedDomainEvent(user.Id));
return user;
}
public void UpdateFirstName(FirstName firstName)
{
if (this.FirstName.Equals(firstName))
throw new UpdateFirstNameException();
this.FirstName = firstName;
}
public void UpdateLastName(LastName lastName)
{
if (this.LastName.Equals(lastName))
throw new UpdateLastNameException();
this.LastName = lastName;
}
public void UpdateBirthDate(BirthDate birthDate)
{
if (this.BirthDate.Equals(birthDate))
throw new UpdateBirthDateException();
this.BirthDate = birthDate;
}
}
About this code snippet:
- It is a
sealed
class: Prevent another class from extends from this one. - Their properties are
private set
, so they must be modified inside the same class instance. - No public
constructor
: Other classes cannot create instances of this class, except for nested classes. - To create instances of this Aggregate Root we do not call the constructor directly, but it uses the Static Factory Method pattern instead.
- It have methods to change the value of the entity’s properties.
- If an attempt is made to update a property using the same value, it throws an exception.