FunctionalDdd.DomainDrivenDesign
3.0.0-alpha.3
dotnet add package FunctionalDdd.DomainDrivenDesign --version 3.0.0-alpha.3
NuGet\Install-Package FunctionalDdd.DomainDrivenDesign -Version 3.0.0-alpha.3
<PackageReference Include="FunctionalDdd.DomainDrivenDesign" Version="3.0.0-alpha.3" />
<PackageVersion Include="FunctionalDdd.DomainDrivenDesign" Version="3.0.0-alpha.3" />
<PackageReference Include="FunctionalDdd.DomainDrivenDesign" />
paket add FunctionalDdd.DomainDrivenDesign --version 3.0.0-alpha.3
#r "nuget: FunctionalDdd.DomainDrivenDesign, 3.0.0-alpha.3"
#:package FunctionalDdd.DomainDrivenDesign@3.0.0-alpha.3
#addin nuget:?package=FunctionalDdd.DomainDrivenDesign&version=3.0.0-alpha.3&prerelease
#tool nuget:?package=FunctionalDdd.DomainDrivenDesign&version=3.0.0-alpha.3&prerelease
Domain Driven Design
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
- SAMPLES.md - Comprehensive examples and patterns
- Main Documentation - Full repository documentation
- Railway Oriented Programming - Result type and functional patterns
License
MIT License - see LICENSE file for details
| Product | Versions 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. |
-
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 |