MartinDrozdik.DDD 0.3.0

There is a newer version of this package available.
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
                    
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.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MartinDrozdik.DDD" Version="0.3.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.3.0
                    
#r "nuget: MartinDrozdik.DDD, 0.3.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.3.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.3.0
                    
Install as a Cake Addin
#tool nuget:?package=MartinDrozdik.DDD&version=0.3.0
                    
Install as a Cake Tool

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:

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 (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, 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 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