MartinDrozdik.DDD 0.4.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package MartinDrozdik.DDD --version 0.4.0
                    
NuGet\Install-Package MartinDrozdik.DDD -Version 0.4.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="MartinDrozdik.DDD" Version="0.4.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MartinDrozdik.DDD" Version="0.4.0" />
                    
Directory.Packages.props
<PackageReference Include="MartinDrozdik.DDD" />
                    
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 MartinDrozdik.DDD --version 0.4.0
                    
#r "nuget: MartinDrozdik.DDD, 0.4.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 MartinDrozdik.DDD@0.4.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=MartinDrozdik.DDD&version=0.4.0
                    
Install as a Cake Addin
#tool nuget:?package=MartinDrozdik.DDD&version=0.4.0
                    
Install as a Cake Tool

Flexible Domain-Driven Design (DDD) library for .NET

NuGet NuGet Downloads Build & Test .NET License

A pragmatic .NET library for Domain-Driven Design that doesn't force you into abstract nonsense (that much). Check the demo.

Installation

dotnet add package MartinDrozdik.DDD

duh...

Philosophy

  • Pragmatic over purist - Use what makes sense, ignore what doesn't
  • Composition over inheritance - Depth is the enemy of maintainability
  • Explicit over implicit - Code should be obvious, not something you need to explain in a meeting
  • Fewer layers, sharper boundaries – Layers exist to solve problems, not to satisfy diagrams or blog posts
  • Type safety - Compiler errors > Runtime errors (reflection is for edge cases, not architecture)
  • Fail fast - Built with validation and error hadling in mind

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. Also, no base classes with 100 methods you don't need. Just the essentials.

Check out the demo for more examples with validation and other goodies:

Value Objects

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

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 conversion support:

public class PersonId(Guid key) : GuidIdentity<PersonId>(key);

// Usage:
interface ISomeService
{
    Person Get(PersonId id);
}
var invoice = someService.Get(id); // Type-safe!

Built-in primitives:

  • GuidIdentity<T> - For when you want GUIDs (use Guid.CreateVersion7() like a civilized person)
  • IntIdentity<T> - For when you're stuck with legacy databases
  • StringIdentity<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. Sometimes it's your fault. Sometimes it's the network. Sometimes it's both.

This library supports both Result<T> and exceptions for error handling in Domain-Driven Design (DDD). Pick the approach that best fits your project, your architecture, and your tolerance for boilerplate.

In general, prefer Result<T> for business operations that can fail in expected ways. This keeps failures explicit, avoids control flow via exceptions, and makes error handling visible instead of somewhere up the call stack.

Exceptions still have their place. APIs, for example, often bubble errors straight to the top anyway, where they're translated into HTTP responses. In those cases, throwing an exception can be the simpler and more honest approach without passing Result<T> throught twelve layers of services.

// 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, because writing the same validation logic twice is a crime:

// Error object:
if (new YourFluentValidator().Validate(someObject).TryGetError(out var error))
{
    return error;
}

// Business exception:
new YourFluentValidator().ValidateAndThrowBusiness(result);

Plenty of extension methods are provided for converting between errors, exceptions, and Result<T> types, so you can stay consistent without reinventing error plumbing every sprint.

Comes with WellKnownErrors for common cases. Feel free to ignore them and make your own (I won't tell).

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. Guys, it's a sacrifice for the type-safety, ok? In my defence, how often will you create new pipes huh?

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 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.

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