Outer.Domain
1.0.0
dotnet add package Outer.Domain --version 1.0.0
NuGet\Install-Package Outer.Domain -Version 1.0.0
<PackageReference Include="Outer.Domain" Version="1.0.0" />
<PackageVersion Include="Outer.Domain" Version="1.0.0" />
<PackageReference Include="Outer.Domain" />
paket add Outer.Domain --version 1.0.0
#r "nuget: Outer.Domain, 1.0.0"
#:package Outer.Domain@1.0.0
#addin nuget:?package=Outer.Domain&version=1.0.0
#tool nuget:?package=Outer.Domain&version=1.0.0
Outer.Domain
A lightweight Domain-Driven Design (DDD) library providing essential building blocks for implementing domain models in .NET applications.
Overview
This library provides base classes and interfaces for implementing core DDD tactical patterns, including entities, value objects, aggregate roots, and domain events.
Core Classes
Entity<TId>
Abstract base class for domain entities with identity-based equality.
Key Features:
- Generic identity type (
TId) for flexible identifier strategies - Identity-based equality comparison (two entities are equal if their IDs match)
- Implements
IEquatable<Entity<TId>>for type-safe comparisons - Overrides
Equals(),GetHashCode(), and equality operators (==,!=)
Usage:
public class User : Entity<Guid>
{
public string Name { get; private set; }
public User(Guid id, string name) : base(id)
{
Name = name;
}
}
Location: Entity.cs:6
ValueObject
Abstract base class for domain value objects with structural equality.
Key Features:
- Value-based equality (objects are equal if all their atomic values match)
- Requires implementation of
GetAtomicValues()to define comparison properties - Implements
IEquatable<ValueObject>for type-safe comparisons - Overrides
Equals(),GetHashCode(), and equality operators (==,!=) - Uses XOR aggregation for hash code calculation
Usage:
public class Address : ValueObject
{
public string Street { get; }
public string City { get; }
public string ZipCode { get; }
public Address(string street, string city, string zipCode)
{
Street = street;
City = city;
ZipCode = zipCode;
}
public override IEnumerable<object> GetAtomicValues()
{
yield return Street;
yield return City;
yield return ZipCode;
}
}
Location: ValueObject.cs:7
IDomainEvent
Interface representing a domain event that occurred in the system.
Key Features:
- Single property:
OccurredOn- timestamp when the event occurred
Usage:
public record UserCreatedEvent(Guid UserId, string Name) : DomainEvent;
Location: IDomainEvent.cs:5
DomainEvent
Abstract base record implementing IDomainEvent with automatic timestamp tracking.
Key Features:
- Automatically sets
OccurredOntoDateTime.UtcNowupon creation - Implemented as a record for immutability and built-in equality
- Suitable for event sourcing and domain event patterns
Usage:
public record OrderPlacedEvent(Guid OrderId, decimal TotalAmount) : DomainEvent;
Location: DomainEvent.cs:5
AggregateRoot<TId>
Abstract base class for aggregate roots that extends Entity<TId> with domain event management.
Key Features:
- Inherits identity-based equality from
Entity<TId> - Internal collection of domain events (
_domainEvents) DomainEvents- read-only access to raised eventsRaiseEvent(IDomainEvent)- adds an event to the collection (null-safe)ClearEvents()- removes all events from the collectionDequeueDomainEvents()- retrieves and clears all events atomically
Usage:
public class Order : AggregateRoot<Guid>
{
public decimal TotalAmount { get; private set; }
public Order(Guid id) : base(id) { }
public void PlaceOrder(decimal amount)
{
TotalAmount = amount;
RaiseEvent(new OrderPlacedEvent(Id, amount));
}
}
Location: AggregateRoot.cs:5
Enumeration
Abstract base class for creating type-safe, behaviour-rich enumerations (smart enums).
Key Features:
- Replaces primitive
enumwith objects that can carry domain logic - Look up members by
Id(FromId<T>) orName(FromName<T>, case-insensitive) - Enumerate all members at runtime with
GetAll<T>() - Value-based equality on
IdandIComparablesupport
Usage:
public class OrderStatus : Enumeration
{
public static readonly OrderStatus Pending = new(0, nameof(Pending));
public static readonly OrderStatus Shipped = new(1, nameof(Shipped));
public static readonly OrderStatus Delivered = new(2, nameof(Delivered));
private OrderStatus(int id, string name) : base(id, name) { }
}
var status = Enumeration.FromName<OrderStatus>("shipped"); // case-insensitive
var all = Enumeration.GetAll<OrderStatus>();
Location: Enumeration.cs
Specification<T>
Encapsulates a business rule as a reusable, composable, and testable object.
Key Features:
- Override
ToExpression()to define the rule as a LINQ expression tree IsSatisfiedBy(entity)for in-memory evaluation- Compose with
And,Or, andNotoperators - Implicit conversion to
Expression<Func<T, bool>>for use with LINQ providers / ORMs
Usage:
public class ActiveUserSpec : Specification<User>
{
public override Expression<Func<User, bool>> ToExpression()
=> user => user.IsActive;
}
public class PremiumUserSpec : Specification<User>
{
public override Expression<Func<User, bool>> ToExpression()
=> user => user.Plan == Plan.Premium;
}
// Compose specifications
var spec = new ActiveUserSpec().And(new PremiumUserSpec());
bool satisfies = spec.IsSatisfiedBy(someUser);
Location: Specification.cs
Result / Result<T>
A lightweight result type for domain operations that can succeed or fail without throwing exceptions.
Key Features:
IsSuccess/IsFailureto check the outcomeErrorcarries the failure message (enforced non-empty on failure)Result<T>.Valuereturns the payload (throwsInvalidOperationExceptionon failure)- Factory methods:
Result.Success(),Result.Failure(error),Result.Success<T>(value),Result.Failure<T>(error)
Usage:
public Result<Order> PlaceOrder(Cart cart)
{
if (cart.Items.Count == 0)
return Result.Failure<Order>("Cannot place an empty order");
var order = new Order(Guid.NewGuid(), cart.Total);
return Result.Success(order);
}
Location: Result.cs
DomainException
Base exception for domain invariant violations, clearly separating domain errors from infrastructure errors.
Usage:
if (quantity <= 0)
throw new DomainException("Order quantity must be positive.");
Location: DomainException.cs
IRepository<TAggregate, TId>
Defines the persistence contract for aggregate roots (the domain "port").
Key Features:
- Generic over aggregate type and identifier type
- Constrained to
AggregateRoot<TId>to enforce DDD rules - Async methods with
CancellationTokensupport:GetByIdAsync,AddAsync,UpdateAsync,DeleteAsync
Usage:
// Domain layer — define the port
public interface IOrderRepository : IRepository<Order, Guid> { }
// Infrastructure layer — provide the adapter
public class OrderRepository : IOrderRepository { /* EF Core implementation */ }
Location: IRepository.cs
IUnitOfWork
Defines a transactional boundary for persisting changes across one or more aggregates.
Usage:
await _orderRepository.AddAsync(order);
await _unitOfWork.SaveChangesAsync(cancellationToken);
Location: IUnitOfWork.cs
IDomainEventHandler<TEvent>
Defines a handler that reacts to a specific type of domain event.
Usage:
public class OrderPlacedHandler : IDomainEventHandler<OrderPlacedEvent>
{
public Task HandleAsync(OrderPlacedEvent domainEvent, CancellationToken ct = default)
{
// Send confirmation email, update read model, etc.
}
}
Location: IDomainEventHandler.cs
Design Principles
Entity vs Value Object
- Entities have a unique identity and lifecycle. Use when tracking changes to an object over time.
- Value Objects are defined by their attributes and are immutable. Use when only the values matter, not the identity.
Aggregate Root
- Acts as the consistency boundary and entry point for a cluster of related objects
- Ensures invariants are maintained across the aggregate
- Collects and manages domain events for event-driven architectures
- External objects can only reference the aggregate root, not internal entities
Domain Events
- Represent something meaningful that happened in the domain
- Immutable and capture the state at the time of occurrence
- Used for decoupling bounded contexts and implementing eventual consistency
- Collected by aggregate roots and published after successful persistence
Example: Complete Aggregate
public class Order : AggregateRoot<Guid>
{
public OrderStatus Status { get; private set; }
public Address ShippingAddress { get; private set; }
public decimal TotalAmount { get; private set; }
public Order(Guid id, Address shippingAddress) : base(id)
{
ShippingAddress = shippingAddress;
Status = OrderStatus.Pending;
RaiseEvent(new OrderCreatedEvent(id));
}
public void Confirm(decimal amount)
{
if (Status != OrderStatus.Pending)
throw new InvalidOperationException("Only pending orders can be confirmed");
TotalAmount = amount;
Status = OrderStatus.Confirmed;
RaiseEvent(new OrderConfirmedEvent(Id, amount));
}
}
public record OrderCreatedEvent(Guid OrderId) : DomainEvent;
public record OrderConfirmedEvent(Guid OrderId, decimal Amount) : DomainEvent;
public class Address : ValueObject
{
public string Street { get; }
public string City { get; }
public Address(string street, string city)
{
Street = street;
City = city;
}
public override IEnumerable<object> GetAtomicValues()
{
yield return Street;
yield return City;
}
}
License
See the project root for license information.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
.NETStandard 2.1
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 106 | 3/18/2026 |