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:

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.