This NuGet package furnishes fundamental building blocks for crafting rich and consistent domain models. It offers inherent support for Value Objects, Entity identification, marking Aggregate Roots, and dispatching domain events.
$ dotnet add package Bruno57.Domain.FoundationsThis NuGet package furnishes fundamental building blocks for crafting rich and consistent domain models. It offers inherent support for Value Objects, Entity identification, marking Aggregate Roots, and dispatching domain events.
✅ EntityBase: A foundational class providing identity management and equality checks for entities.
✅ ValueObject: An abstract class for constructing immutable value objects with intrinsic structural equality.
✅ [AggregateRoot] Attribute: A declarative approach to designate aggregate roots without relying on empty interfaces.
✅ Domain Event Infrastructure: Mechanisms for publishing and processing significant domain occurrences.
Install via NuGet:
dotnet add package Bruno57.Domain.Foundations
A base class for your domain entities, equipped with identity comparison and equality operations.
An abstract blueprint for creating immutable value objects where equality is determined by their properties. For example:
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;
}
}
This attribute offers a cleaner alternative to the traditional IAggregateRoot interface for identifying aggregate roots.
[AggregateRoot]
public class Order : EntityBase
{
// Domain logic...
}
In conventional Domain-Driven Design, a common practice involves defining an empty interface like so:
public interface IAggregateRoot { }
And then having aggregates implement it:
public class Order : EntityBase, IAggregateRoot
{
// Domain logic
}
This interface serves solely as a marker, lacking any methods or properties. However, code analysis tools (such as Roslyn analyzers, ReSharper, and StyleCop) often flag this pattern with warnings like:
IDE0067 / CA1040: "Do not declare empty interfaces."
This is primarily because:
Empty interfaces don't express any behavior, which is a fundamental purpose of interfaces in object-oriented programming. They can be challenging to inspect programmatically and might introduce ambiguity in larger systems. Attributes often provide a more suitable mechanism for attaching metadata.
By adopting a custom attribute, you embrace a more semantically precise and analyzer-friendly method for tagging metadata.
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class AggregateRootAttribute : Attribute
{
}
If you’re doing something like domain scanning (e.g. identifying aggregate roots at startup or validation), you can reflect on this attribute easily:
var aggregateRootTypes = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(t => t.GetCustomAttribute<AggregateRootAttribute>() != null)
.ToList();
This can be useful in:
Built-in support to create, add, and consume domain events from aggregates.
public class User : EntityBase
{
public string Email { get; private set; }
public User(int id, string email)
: base(id)
{
Email = email;
AddDomainEvent(new UserCreatedDomainEvent(id, email));
}
}
Use this package in your domain layer when you want to:
💡 This library follows core DDD principles:
📚 Further Exploration
To delve deeper into the concepts of Domain-Driven Design, consider these resources: