DDD building blocks for .NET — Entity, ValueObject, AggregateRoot, Id, Guard clauses, and more.
public record OrderId(Guid Value) : Id<OrderId>(Value);public class OrderItem : Entity<Guid>
{
public string ProductName { get; }
public int Quantity { get; }
public OrderItem(Guid id, string productName, int quantity) : base(id)
{
ProductName = DomainGuard.AgainstNullOrWhiteSpace(productName, nameof(productName));
Quantity = (int)DomainGuard.AgainstNegativeOrZero(quantity, nameof(quantity));
}
}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?> GetEqualityComponents()
{
yield return Amount;
yield return Currency;
}
}public class Order : AggregateRoot<OrderId>
{
public string CustomerName { get; private set; }
public static Order Create(string customerName)
{
var order = new Order(OrderId.New(), customerName);
order.RaiseDomainEvent(new OrderCreatedEvent(order.Id));
return order;
}
private Order(OrderId id, string customerName) : base(id)
{
CustomerName = DomainGuard.AgainstNullOrWhiteSpace(customerName, nameof(customerName));
}
private Order() { CustomerName = string.Empty; } // ORM
}public class OrderStatus : Enumeration<OrderStatus>
{
public static readonly OrderStatus Pending = new(0, "Pending");
public static readonly OrderStatus Confirmed = new(1, "Confirmed");
public static readonly OrderStatus Shipped = new(2, "Shipped");
private OrderStatus(int value, string name) : base(value, name) { }
}
// Usage
var status = OrderStatus.FromName("Confirmed");
var all = OrderStatus.GetAll();protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.ConfigureIdConventions();
configurationBuilder.ConfigureEnumerationConventions<OrderStatus>();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.IgnoreDomainEvents();
}var interceptor = new DomainEventDispatchInterceptor(async (evt, ct) =>
{
// dispatch to MediatR, event bus, etc.
await mediator.Publish(evt, ct);
});
services.AddDbContext<AppDbContext>(opts =>
opts.AddInterceptors(interceptor));DomainGuard provides 12 fluent validation methods that all return the validated value:
Name = DomainGuard.AgainstNullOrWhiteSpace(name, nameof(name));
Email = DomainGuard.AgainstInvalidEmail(email, nameof(email));
Quantity = (int)DomainGuard.AgainstNegativeOrZero(quantity, nameof(quantity));
Title = DomainGuard.AgainstLength(title, 200, nameof(title));All throw DomainValidationException with a ParameterName property for structured API error responses.
DomainException— base domain errorDomainValidationException— validation failure withParameterNameEntityNotFoundException— entity not found withEntityTypeandEntityIdForbiddenException— authorization failure
throw EntityNotFoundException.For<Order>(orderId);dotnet build
dotnet test
dotnet pack -c Release -o ./nupkg