FunctionalDdd.DomainDrivenDesign 3.0.0-alpha.3

This is a prerelease version of FunctionalDdd.DomainDrivenDesign.
dotnet add package FunctionalDdd.DomainDrivenDesign --version 3.0.0-alpha.3
                    
NuGet\Install-Package FunctionalDdd.DomainDrivenDesign -Version 3.0.0-alpha.3
                    
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="FunctionalDdd.DomainDrivenDesign" Version="3.0.0-alpha.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FunctionalDdd.DomainDrivenDesign" Version="3.0.0-alpha.3" />
                    
Directory.Packages.props
<PackageReference Include="FunctionalDdd.DomainDrivenDesign" />
                    
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 FunctionalDdd.DomainDrivenDesign --version 3.0.0-alpha.3
                    
#r "nuget: FunctionalDdd.DomainDrivenDesign, 3.0.0-alpha.3"
                    
#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 FunctionalDdd.DomainDrivenDesign@3.0.0-alpha.3
                    
#: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=FunctionalDdd.DomainDrivenDesign&version=3.0.0-alpha.3&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FunctionalDdd.DomainDrivenDesign&version=3.0.0-alpha.3&prerelease
                    
Install as a Cake Tool

Domain Driven Design

NuGet Package

Building blocks for implementing Domain-Driven Design tactical patterns in C# with functional programming principles.

Installation

dotnet add package FunctionalDDD.DomainDrivenDesign

Quick Start

Entity

Objects with unique identity. Equality based on ID.

public class CustomerId : ScalarValueObject<Guid>
{
    private CustomerId(Guid value) : base(value) { }
    public static CustomerId NewUnique() => new(Guid.NewGuid());
}

public class Customer : Entity<CustomerId>
{
    public string Name { get; private set; }
    
    private Customer(CustomerId id, string name) : base(id)
    {
        Name = name;
    }
    
    public static Result<Customer> TryCreate(string name) =>
        name.ToResult()
            .Ensure(n => !string.IsNullOrWhiteSpace(n), Error.Validation("Name required"))
            .Map(n => new Customer(CustomerId.NewUnique(), n));
}

Value Object

Immutable objects with no identity. Equality based on all properties.

public class Money : ValueObject
{
    public decimal Amount { get; }
    public string Currency { get; }
    
    private Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }
    
    public static Result<Money> TryCreate(decimal amount, string currency = "USD") =>
        (amount, currency).ToResult()
            .Ensure(x => x.amount >= 0, Error.Validation("Amount cannot be negative"))
            .Map(x => new Money(x.amount, x.currency));
    
    protected override IEnumerable<IComparable> GetEqualityComponents()
    {
        yield return Amount;
        yield return Currency;
    }
    
    public Money Add(Money other) =>
        Currency == other.Currency
            ? new Money(Amount + other.Amount, Currency)
            : throw new InvalidOperationException("Currency mismatch");
}

Aggregate

Cluster of entities and value objects treated as a unit. Manages domain events.

public record OrderCreatedEvent(OrderId Id, CustomerId CustomerId) : IDomainEvent;
public record OrderSubmittedEvent(OrderId Id, Money Total) : IDomainEvent;

public class Order : Aggregate<OrderId>
{
    private readonly List<OrderLine> _lines = [];
    
    public CustomerId CustomerId { get; }
    public IReadOnlyList<OrderLine> Lines => _lines.AsReadOnly();
    public Money Total { get; private set; }
    public OrderStatus Status { get; private set; }
    
    private Order(OrderId id, CustomerId customerId) : base(id)
    {
        CustomerId = customerId;
        Status = OrderStatus.Draft;
        Total = Money.TryCreate(0).Value;
        DomainEvents.Add(new OrderCreatedEvent(id, customerId));
    }
    
    public static Result<Order> TryCreate(CustomerId customerId) =>
        new Order(OrderId.NewUnique(), customerId).ToResult();
    
    public Result<Order> AddLine(ProductId productId, string name, Money price, int qty) =>
        this.ToResult()
            .Ensure(_ => Status == OrderStatus.Draft, Error.Validation("Order not editable"))
            .Ensure(_ => qty > 0, Error.Validation("Quantity must be positive"))
            .Tap(_ =>
            {
                _lines.Add(new OrderLine(productId, name, price, qty));
                RecalculateTotal();
            });
    
    public Result<Order> Submit() =>
        this.ToResult()
            .Ensure(_ => Status == OrderStatus.Draft, Error.Validation("Already submitted"))
            .Ensure(_ => Lines.Count > 0, Error.Validation("Cannot submit empty order"))
            .Tap(_ =>
            {
                Status = OrderStatus.Submitted;
                DomainEvents.Add(new OrderSubmittedEvent(Id, Total));
            });
    
    private void RecalculateTotal()
    {
        var total = Lines.Sum(l => l.Price.Amount * l.Quantity);
        Total = Money.TryCreate(total).Value;
    }
}

Domain Events

Publish events after persisting:

var order = Order.TryCreate(customerId)
    .Bind(o => o.AddLine(productId, "Widget", price, 5))
    .Bind(o => o.Submit());

if (order.IsSuccess)
{
    await repository.SaveAsync(order.Value);
    
    foreach (var evt in order.Value.UncommittedEvents())
    {
        await eventBus.PublishAsync(evt);
    }
    
    order.Value.AcceptChanges();
}

Best Practices

1. Use entities when identity matters

public class Customer : Entity<CustomerId> { } // Identity-based
public class Address : ValueObject { }          // Value-based

2. Keep aggregates small

public class Order : Aggregate<OrderId>
{
    // ? Include: OrderLine (part of aggregate)
    // ? Exclude: Customer, Shipment (reference by ID)
    public CustomerId CustomerId { get; }
}

3. Reference other aggregates by ID

// ? Good
public CustomerId CustomerId { get; }

// ? Avoid
public Customer Customer { get; }

4. Enforce invariants in aggregate root

public Result<Order> AddLine(...) =>
    this.ToResult()
        .Ensure(_ => Status == OrderStatus.Draft, ...)
        .Ensure(_ => quantity > 0, ...)
        .Tap(_ => _lines.Add(...));

5. Use domain events for side effects

// ? Good - domain event
DomainEvents.Add(new OrderSubmittedEvent(Id));

// ? Avoid - direct coupling
_emailService.SendConfirmation();

6. Validate using Result types

// ? Good
public Result<Order> Cancel(string reason) =>
this.ToResult()
    .Ensure(_ => Status == OrderStatus.Draft, ...);

// ? Avoid
if (Status != OrderStatus.Draft)
    throw new InvalidOperationException(...);

7. Make value objects immutable

// ? Good
public decimal Amount { get; }  // No setter

// ? Avoid
public decimal Amount { get; set; }

Core Concepts

Entity<TId>

  • Identity-based equality
  • Mutable state
  • Lifecycle tracked by ID

ValueObject

  • No identity
  • Immutable
  • Equality based on all properties
  • Override GetEqualityComponents()

ScalarValueObject<T>

  • Wraps single value
  • Type safety for primitives
  • Implicit conversion to T

Aggregate<TId>

  • Consistency boundary
  • Manages domain events
  • Properties: IsChanged, UncommittedEvents(), AcceptChanges()

IDomainEvent

  • Marker interface for domain events
  • Use records for immutability
  • Publish after persistence

Resources

License

MIT License - see LICENSE file for details

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on FunctionalDdd.DomainDrivenDesign:

Package Downloads
FunctionalDdd.CommonValueObjects

To avoid passing around strings, it is recommended to use RequiredString to obtain strongly typed properties. The source code generator will automate the implementation process.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0-alpha.3 104 12/20/2025
2.1.10 674 12/3/2025
2.1.9 270 11/21/2025
2.1.1 237 4/26/2025
2.1.0-preview.3 87 4/26/2025
2.0.1 233 1/23/2025
2.0.0-alpha.62 81 1/8/2025
2.0.0-alpha.61 84 1/7/2025
2.0.0-alpha.60 97 12/7/2024
2.0.0-alpha.55 85 11/22/2024
2.0.0-alpha.52 85 11/7/2024
2.0.0-alpha.48 84 11/2/2024
2.0.0-alpha.47 87 10/30/2024
2.0.0-alpha.44 151 10/18/2024
2.0.0-alpha.42 98 10/14/2024
2.0.0-alpha.39 128 6/27/2024
2.0.0-alpha.38 107 4/24/2024
2.0.0-alpha.33 96 4/17/2024
2.0.0-alpha.26 132 4/9/2024
2.0.0-alpha.21 106 4/1/2024
2.0.0-alpha.19 101 3/5/2024
2.0.0-alpha.18 95 2/28/2024
2.0.0-alpha.17 98 2/26/2024
2.0.0-alpha.15 104 1/30/2024
2.0.0-alpha.8 92 1/27/2024
2.0.0-alpha.6 127 1/5/2024
1.1.1 802 11/15/2023
1.1.0-alpha.32 158 11/2/2023
1.1.0-alpha.30 239 10/31/2023
1.1.0-alpha.28 118 10/28/2023
1.1.0-alpha.27 118 10/28/2023
1.1.0-alpha.24 117 10/20/2023
1.1.0-alpha.23 123 10/13/2023
1.1.0-alpha.21 155 10/1/2023
1.1.0-alpha.20 116 9/30/2023
1.1.0-alpha.19 144 9/30/2023
1.1.0-alpha.18 136 9/29/2023
1.1.0-alpha.17 111 9/22/2023
1.1.0-alpha.13 107 9/16/2023
1.1.0-alpha.4 229 6/9/2023
1.1.0-alpha.3 161 6/8/2023
1.0.1 854 5/12/2023
0.1.0-alpha.40 207 4/6/2023
0.1.0-alpha.39 219 4/3/2023
0.1.0-alpha.38 243 4/2/2023
0.1.0-alpha.37 219 3/31/2023
0.1.0-alpha.35 212 3/29/2023
0.1.0-alpha.34 187 3/28/2023
0.1.0-alpha.32 226 3/18/2023
0.1.0-alpha.30 220 3/11/2023
0.1.0-alpha.27 223 3/7/2023
0.1.0-alpha.24 223 2/15/2023
0.1.0-alpha.22 217 2/15/2023
0.1.0-alpha.20 214 2/13/2023
0.1.0-alpha.19 179 2/13/2023
0.0.1-alpha.14 239 1/4/2023
0.0.1-alpha.4 210 12/30/2022