The TaskHub.Shared.Domain module provides the building blocks for implementing Domain-Driven Design (DDD) patterns.
An Aggregate is a cluster of domain objects that can be treated as a single unit. Every aggregate has a Root, which is the only member of the aggregate that outside objects are allowed to hold a reference to.
public abstract class AggregateBase<T>(T id) : IAggregate
{
public T Id { get; protected set; } = id;
// Internal collection of events
protected readonly List<IDomainEvent> MyEvents = [];
// Public read-only access
public IReadOnlyCollection<IDomainEvent> Events => MyEvents.AsReadOnly();
// Method to clear events after dispatch
public void ClearEvents() => MyEvents.Clear();
}
This example demonstrates an aggregate with child entities and invariant checks.
public class Project : AggregateBase<Guid>
{
public string Name { get; private set; }
private readonly List<TaskItem> _tasks = new();
public IReadOnlyCollection<TaskItem> Tasks => _tasks.AsReadOnly();
public Project(Guid id, string name) : base(id)
{
if (string.IsNullOrWhiteSpace(name))
throw new DomainException(ProjectErrors.InvalidName);
Name = name;
MyEvents.Add(new ProjectCreated(id, name));
}
public void AddTask(string title, string description)
{
if (_tasks.Count >= 100)
throw new DomainException(ProjectErrors.ProjectFull);
var task = new TaskItem(Guid.NewGuid(), title, description);
_tasks.Add(task);
MyEvents.Add(new TaskAddedToProject(Id, task.Id));
}
}
public class TaskItem(Guid id, string title, string description)
{
public Guid Id { get; } = id;
public string Title { get; } = title;
public string Description { get; } = description;
}
Domain events are simple POCOs that implement IDomainEvent.
public record ProjectCreated(Guid ProjectId, string Name) : DomainEventBase, IDomainEvent;
Events in TaskHub.Shared include metadata like CreatedAt and can be versioned using the [EventVersion] attribute.