MartinDrozdik.DDD
0.3.0
See the version list below for details.
dotnet add package MartinDrozdik.DDD --version 0.3.0
NuGet\Install-Package MartinDrozdik.DDD -Version 0.3.0
<PackageReference Include="MartinDrozdik.DDD" Version="0.3.0" />
<PackageVersion Include="MartinDrozdik.DDD" Version="0.3.0" />
<PackageReference Include="MartinDrozdik.DDD" />
paket add MartinDrozdik.DDD --version 0.3.0
#r "nuget: MartinDrozdik.DDD, 0.3.0"
#:package MartinDrozdik.DDD@0.3.0
#addin nuget:?package=MartinDrozdik.DDD&version=0.3.0
#tool nuget:?package=MartinDrozdik.DDD&version=0.3.0
Flexible Domain-Driven Design (DDD) library for .NET
A pragmatic .NET library for Domain-Driven Design that doesn't force you into abstract nonsense (that much). Check the demo app for recommended usage.
Installation
dotnet add package MartinDrozdik.DDD
Philosophy
- Pragmatic over purist - Use what makes sense, ignore what doesn't
- Type safety - Compiler errors > Runtime errors (screw reflection)
- Explicit over implicit - Code should be obvious
- Composition over inheritance - Most templates are interfaces for a reason
- Fail fast - Validation at boundaries, not 67 layers deep
Use the parts that help. Ignore the rest. Good luck.
Demo Project
The demo project shows recommended patterns. It's not gospel, but it works. Check out:
- Models/ - Aggregates, Entities, Value Objects, Enumerations
- Requests/ - Commands and Queries with handlers
- Context/ - EF Core configuration with identity converters
Templates (keeping it simple, stupid)
Basic interfaces that define DDD building blocks. These are interfaces, not abstract classes, because your ORM probably needs that flexibility anyway.
Check out the demo app for more examples with validation and other goodies:
- Person entity with validation and strongly typed ID
- Invoice aggregate with validation and strongly typed ID
- Value Object with validation for an Invoice Number
- In-Memory state enumeration
Value Objects
Things compared by value, not identity:
public class InvoiceNumber : ValueObject
{
private InvoiceNumber(int year, int order)
{
Year = year;
Order = order;
}
public int Year { get; }
public int Order { get; }
public static InvoiceNumber Create(int year, int order)
{
var result = new InvoiceNumber(year, order);
return result;
}
// Takes care of equality and hashing based on the properties you yield here:
protected override IEnumerable<object?> GetEqualityComponents()
{
yield return Year;
yield return Order;
}
}
Entities
Things compared by identity, not value. Just implement the marker interface and you're good to go:
public class Person : IDomainEntity<Guid> // or IAggregateRoot<Guid>
{
private Person(Guid id, string fullName)
{
Id = id;
FullName = fullName;
}
public Guid Id { get; }
public string FullName { get; }
public static Person Create(string fullName)
{
// Validate...
return new Person(new PersonId(Guid.CreateVersion7()), fullName);
}
}
Aggregates
Your consistency boundaries (aka "units of work that make sense").
Same implementation as Entity, just a different marker interface IAggregateRoot<Guid>.
Identities
Strongly-Typed IDs (No More Guid Soup).
Stop passing around naked Guids and ints like it's 2010. Type-safe identities with implicit conversion support.
public class PersonId(Guid key) : GuidIdentity<PersonId>(key);
// Usage:
var invoice = someService.Get(id); // Type-safe!
Built-in primitives:
GuidIdentity<T>- For when you want GUIDs (useGuid.CreateVersion7()like a civilized person)IntIdentity<T>- For when you're stuck with legacy databasesStringIdentity<T>- For when external systems hate you
EF Core support:
builder.Property(e => e.Id)
.HasIdentityConvertor(new IdentityConverter<InvoiceId, Guid>());
Enumerations
Because enum is fine until you need behavior and more properties, then you're screwed.
public class InvoiceState(EnumerationName name /*and more properties as you like*/)
: Enumeration(name)
{
public static InvoiceState Draft => new(new EnumerationName("Draft"));
public static InvoiceState Issued => new(new EnumerationName("Issued"));
public static InvoiceState Paid => new(new EnumerationName("Paid"));
// Add methods, validations, whatever you need
public bool CanBeModified() => this == Draft;
}
// Usage
var state = InvoiceState.Draft;
if (state.CanBeModified()) { /* ... */ }
Serializes to strings/properties in your DB/JSON. Compares by value. Extends like a class. It's beautiful 😍.
Exceptions and Errors
Stuff happens, and you need to handle it.
This library provides support for both Result and Exception handling strategies in Domain-Driven Design (DDD). You can choose the approach that best fits your project's needs.
Normally, you would use Result<T> types to represent the outcome of business operations that can fail, allowing you to handle errors in a functional way without throwing exceptions. This is particularly useful in scenarios where you want to avoid the overhead of exceptions and prefer to work with explicit success/failure states.
However, applications like APIs usually propagate the error all the way to the top level anyway, where exceptions can be caught and translated into appropriate HTTP responses. In such cases, using exceptions might be more straightforward without tons of boilerplate.
// Build errors fluently
var error = new ErrorBuilder()
.WithCode("InvalidInvoiceNumber")
.WithMessage("Invoice number must be after year 2000")
.WithDetail("Year", year.ToString())
// More details!: .WithDetail("Status", "caffeine overdose")
.Build();
// Convert to exceptions when you want to
throw error.ToBusinessRuleException();
Integrates well with FluentValidation:
// Error object:
if (new YourFluentValidator().Validate(someObject).TryGetError(out var error))
{
return error;
}
// Business exception:
new YourFluentValidator().ValidateAndThrowBusiness(result);
Many more extension methods for converting between errors and exceptions, and for working with Result<T> types.
Comes with WellKnownErrors for common cases. Feel free to ignore them and make your own.
Mediator - CQRS Without the Ceremony
Simple mediator pattern for Commands and Queries. No magic, just delegates work to handlers via DI.
Define a command:
public record CreateInvoiceCommand(/*params...*/) : ICommand<InvoiceId>;
Handle it:
public class CreateInvoiceCommandHandler : ICommandHandler<CreateInvoiceCommand, InvoiceId>
{
public async Task<InvoiceId> HandleAsync(
CreateInvoiceCommand command,
CancellationToken cancellationToken)
{
// Code
}
}
Register it:
builder.Services.AddMediator(config =>
{
config.WithCommand<CreateInvoiceCommand, InvoiceId, CreateInvoiceCommandHandler>(integration);
// ...
});
Send it:
var invoiceId = await mediator.SendCommand<CreateInvoiceCommand, InvoiceId>(
new CreateInvoiceCommand(/*params*/),
cancellationToken
);
Pipelines – we got them! And they are type-safe and nice! Pick who uses what pipelines! Add cross-cutting concerns like logging, validation, transactions, etc. without cluttering your handlers.
Using pipelines is super nice; but, to be honest, defining new pipelines requires a bit of boilerplate. It's a sacrifice for the type-safety guys...
Check out the demo app for examples of pipelines in action:
builder.Services.AddMediator(config =>
{
var integration = new LoggingPipelineIntegrator()
.Merge<ValidationPipelineIntegrator>();
config.WithCommand<CreateInvoiceCommand, InvoiceId, CreateInvoiceCommandHandler>(integration);
// ...
});
| 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
- CSharpFunctionalExtensions (>= 3.6.0)
- FluentValidation (>= 12.1.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on MartinDrozdik.DDD:
| Package | Downloads |
|---|---|
|
MartinDrozdik.DDD.Web
A library to support web domain-driven design development with solutions to common problems. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.7.0 | 281 | 5/16/2026 |
| 0.6.0 | 161 | 5/2/2026 |
| 0.5.3 | 174 | 4/19/2026 |
| 0.5.1 | 161 | 4/9/2026 |
| 0.5.0 | 164 | 4/6/2026 |
| 0.4.4 | 137 | 4/6/2026 |
| 0.4.3 | 93 | 4/6/2026 |
| 0.4.2 | 141 | 4/6/2026 |
| 0.4.1 | 146 | 4/6/2026 |
| 0.4.0 | 110 | 4/5/2026 |
| 0.3.3 | 179 | 3/21/2026 |
| 0.3.2 | 216 | 2/13/2026 |
| 0.3.1 | 138 | 2/9/2026 |
| 0.3.0 | 122 | 2/8/2026 |
| 0.2.0 | 123 | 1/18/2026 |
| 0.1.0 | 117 | 1/17/2026 |