DDD Building Blocks: Entities, Aggregates, and Value Objects
MASA Framework DDD building blocks give you Entity, AggregateRoot, ValueObject, DomainService, and IRepository abstractions for domain modeling in .NET.
Use this file to discover all available pages before exploring further.
MASA Framework ships a complete set of Domain-Driven Design building blocks that let you model your business domain using well-established patterns. The abstractions live in Masa.BuildingBlocks.Ddd.Domain — a package with no infrastructure dependencies — so your domain model stays clean, portable, and testable regardless of which database or transport technology you choose.
Entity is the abstract base for any object whose identity is defined by its key(s) rather than its attribute values. It implements IEntity, IEquatable<Entity>, and IEquatable<object>.
AggregateRoot extends Entity and adds domain event management via IGenerateDomainEvents. It maintains an internal _domainEvents list that is populated during domain operations and flushed by the Unit of Work before committing.
Method
Description
AddDomainEvent(IDomainEvent)
Appends a domain event to the internal queue
RemoveDomainEvent(IDomainEvent)
Removes a specific event from the queue
GetDomainEvents()
Returns the current queued events (read-only)
ClearDomainEvents()
Empties the event queue (called automatically by the UoW)
AggregateRoot<TKey> provides the typed Id property from Entity<TKey>.
public class Order : AggregateRoot<Guid>{ private readonly List<OrderItem> _items = new(); public IReadOnlyList<OrderItem> Items => _items.AsReadOnly(); public string Status { get; private set; } = "Pending"; public Order(Guid id) : base(id) { } public void AddItem(OrderItem item) { _items.Add(item); AddDomainEvent(new OrderItemAddedEvent(Id, item)); }}
ValueObject is the base for objects whose identity is defined entirely by their attribute values — two instances with the same values are equal. Implement GetEqualityValues() to declare which properties participate in equality.
public class Money : ValueObject{ public decimal Amount { get; } public string Currency { get; } public Money(decimal amount, string currency) { Amount = amount; Currency = currency; } protected override IEnumerable<object> GetEqualityValues() { yield return Amount; yield return Currency; }}// Value equality — no identity comparison neededvar price1 = new Money(9.99m, "USD");var price2 = new Money(9.99m, "USD");Console.WriteLine(price1 == price2); // true
MASA Framework provides pre-built auditing base classes that automatically track who created or modified an entity and when. These work in conjunction with MasaDbContext which sets the values on SaveChangesAsync.
AuditEntity<TKey, TUserId>
Entity with Creator, Modifier (typed TUserId), CreationTime, and ModificationTime properties.
AuditAggregateRoot<TKey, TUserId>
Same auditing as above, but extends AggregateRoot so it also supports domain events.
FullEntity<TKey, TUserId>
Extends AuditEntity with IsDeleted for soft-delete support.
FullAggregateRoot<TKey, TUserId>
Extends AuditAggregateRoot with IsDeleted — the most complete base class.
// Entity with full auditing and soft-delete, keyed by Guid with Guid user IDpublic class Product : FullAggregateRoot<Guid, Guid>{ public string Name { get; private set; } = string.Empty; public decimal Price { get; private set; } // Inherited: Id, Creator, CreationTime, Modifier, ModificationTime, IsDeleted public Product(Guid id, string name, decimal price) : base(id) { Name = name; Price = price; }}
IRepository<TEntity> is the generic repository contract. The EF Core implementation is automatically registered when you call AddDomainEventBus() after setting up MasaDbContext.
DomainService is the base class for stateless domain logic that coordinates multiple aggregates. It receives an IDomainEventBus through its constructor or via SetDomainEventBus(), which is called automatically during DI registration.
public class OrderDomainService : DomainService{ private readonly IOrderRepository _orderRepo; public OrderDomainService( IDomainEventBus eventBus, IOrderRepository orderRepo) : base(eventBus) { _orderRepo = orderRepo; } public async Task CancelOrderAsync(Guid orderId) { var order = await _orderRepo.FindAsync(orderId) ?? throw new InvalidOperationException("Order not found."); order.Cancel(); // raises OrderCancelledEvent internally await _orderRepo.UpdateAsync(order); }}
Register all domain services by calling AddDomainEventBus():
AddDomainEventBus() scans all loaded assemblies for DomainService subclasses and registers them as scoped services automatically — no manual AddScoped<MyDomainService>() calls needed.