This package contains some base classes to easily get started writing domains and DDD supported software systems. The domain model contains change tracking for persistence convenience.
$ dotnet add package HexMaster.DomainDrivenDesignWelcome to the Domain Driven Design Support Library, a NuGet package designed to simplify the implementation of Domain-Driven Design (DDD) in your projects. This library abstracts away the base plumbing required for DDD, allowing you to focus on your core domain logic.
IDomainModel interface to define domain models.DomainModel<T> to streamline common operations for domain models.Add the NuGet package to your project:
Install-Package HexMaster.DomainDrivenDesign
The core of this library is the IDomainModel interface. It provides the foundation for all domain models in your application.
For most use cases, you can inherit from the DomainModel<T> base class, which already implements IDomainModel and provides useful base functionality.
Here's an example of creating a domain model for an Order entity:
using HexMaster.DomainDrivenDesign;
public class Order : DomainModel<Guid>
{
public string CustomerName { get; private set; }
public DateTime OrderDate { get; private set; }
public Order(Guid id, string customerName, DateTime orderDate) : base(id)
{
CustomerName = customerName;
OrderDate = orderDate;
}
public void UpdateCustomerName(string newCustomerName)
{
if (string.IsNullOrWhiteSpace(newCustomerName))
throw new ArgumentException("Customer name cannot be empty.");
CustomerName = newCustomerName;
}
}
The DomainModel<T> base class simplifies your domain model by:
T.Order order1 = new Order(Guid.NewGuid(), "John Doe", DateTime.UtcNow);
Order order2 = new Order(order1.Id, "John Doe", DateTime.UtcNow);
// Equality check based on ID
Console.WriteLine(order1 == order2); // True
Domain events are a key concept in Domain-Driven Design, allowing you to capture and react to significant changes or events within your domain. This library provides built-in support for domain events, enabling easy integration and handling.
All domain events in this library must implement the IDomainEvent interface. This interface represents an event that has occurred within your domain.
public interface IDomainEvent
{
DateTime OccurredOn { get; }
}
The DomainModel<T> base class includes a method AddDomainEvent for raising domain events. These events can be processed later by the Domain Event Dispatcher.
using HexMaster.DomainDrivenDesign;
public class Order : DomainModel<Guid>
{
public string CustomerName { get; private set; }
public Order(Guid id, string customerName) : base(id)
{
CustomerName = customerName;
}
public void PlaceOrder()
{
// Raise a domain event
AddDomainEvent(new OrderPlacedEvent(Id));
}
}
public class OrderPlacedEvent : IDomainEvent
{
public Guid OrderId { get; }
public DateTime OccurredOn { get; } = DateTime.UtcNow;
public OrderPlacedEvent(Guid orderId)
{
OrderId = orderId;
}
}
The Domain Event Dispatcher processes and dispatches domain events to their corresponding handlers. It integrates with Dependency Injection (DI) to resolve and invoke event handlers automatically.
To use the Domain Event Dispatcher, register it in your application startup:
services.AddDomainEventDispatcher();
Event handlers process domain events when they are dispatched. Handlers must inherit from the DomainEventHandler<TEvent> base class, where TEvent is the type of domain event to handle.
The DomainEventHandler<TEvent> base class provides an abstract Handle method that you override to define your handling logic.
using DDD.Support.Library;
using System.Threading.Tasks;
public class OrderPlacedEventHandler : DomainEventHandler<OrderPlacedEvent>
{
public override Task Handle(OrderPlacedEvent domainEvent)
{
// Handle the event (e.g., send an email, update a database, etc.)
Console.WriteLine($"Order placed with ID: {domainEvent.OrderId}");
return Task.CompletedTask;
}
}
AddDomainEvent in your domain model.DomainEventHandler<TEvent> and overriding the Handle method.Handle method.The IDomainModel interface is the contract for all domain models in your application. It ensures that all domain models have an ID and enforce a basic equality structure.
public interface IDomainModel
{
object Id { get; }
}
The DomainModel<T> class provides a base implementation of IDomainModel and includes:
Equals, GetHashCode, ==, !=).AddDomainEvent.public abstract class DomainModel<T> : IDomainModel
{
public T Id { get; }
private readonly List<IDomainEvent> _domainEvents = new();
public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
protected DomainModel(T id)
{
if (id == null || id.Equals(default(T)))
throw new ArgumentException("ID cannot be null or default.");
Id = id;
}
protected void AddDomainEvent(IDomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
}
public void ClearDomainEvents() => _domainEvents.Clear();
public override bool Equals(object obj)
{
if (obj is DomainModel<T> other)
{
return EqualityComparer<T>.Default.Equals(Id, other.Id);
}
return false;
}
public override int GetHashCode() => EqualityComparer<T>.Default.GetHashCode(Id);
public static bool operator ==(DomainModel<T> a, DomainModel<T> b) =>
a?.Equals(b) ?? b is null;
public static bool operator !=(DomainModel<T> a, DomainModel<T> b) => !(a == b);
object IDomainModel.Id => Id;
}
If the DomainModel<T> class does not meet your specific requirements, you can:
IDomainModel interface directly.DomainModel<T> with additional functionality.Contributions are welcome! Feel free to submit issues, feature requests, or pull requests on the GitHub repository.
This project is licensed under the MIT License. See the LICENSE file for details.
If you encounter any issues or have suggestions, please open an issue on GitHub or contact the maintainers directly.
Start building your next Domain-Driven Design project with confidence using the Domain Driven Design Support Library!