Outer.Domain 1.0.0

dotnet add package Outer.Domain --version 1.0.0
                    
NuGet\Install-Package Outer.Domain -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Outer.Domain" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Outer.Domain" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Outer.Domain" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Outer.Domain --version 1.0.0
                    
#r "nuget: Outer.Domain, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Outer.Domain@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Outer.Domain&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Outer.Domain&version=1.0.0
                    
Install as a Cake Tool

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 OccurredOn to DateTime.UtcNow upon 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 events
  • RaiseEvent(IDomainEvent) - adds an event to the collection (null-safe)
  • ClearEvents() - removes all events from the collection
  • DequeueDomainEvents() - 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 enum with objects that can carry domain logic
  • Look up members by Id (FromId<T>) or Name (FromName<T>, case-insensitive)
  • Enumerate all members at runtime with GetAll<T>()
  • Value-based equality on Id and IComparable support

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, and Not operators
  • 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 / IsFailure to check the outcome
  • Error carries the failure message (enforced non-empty on failure)
  • Result<T>.Value returns the payload (throws InvalidOperationException on 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 CancellationToken support: 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .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