ezDDD.Entity 1.0.0

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

ezDDD.NET

Tactical Domain-Driven Design patterns library for .NET 8+

Based on Java ezddd 6.0.1

A modern tactical DDD library for .NET with event sourcing, state sourcing, and CQRS patterns. This is a faithful .NET port of the Java ezddd 6.0.1 library (GitLab commit: 3aac0f5) with ~99% semantic parity and .NET-specific improvements.

Build and Test NuGet .NET License Status


Status

ezDDD.NET follows Semantic Versioning. The first public release is in preparation (not yet published to NuGet); once published, the current version is shown by the NuGet badge above. Changes between releases are tracked in CHANGELOG.md.


Table of Contents


Quick Start

Installation

dotnet add package ezDDD.Core

ezDDD.Core is the all-in-one aggregator; individual modules (ezDDD.Common, ezDDD.Entity, ezDDD.UseCase, ezDDD.Cqrs) can also be installed separately — see Modules.

Basic Usage: Event-Sourced Aggregate

using EzDdd.Entity;

// Aggregate ID (ToString() drives the stream name: "account-ACC-001")
public sealed record AccountId(string Value) : IValueObject
{
    public override string ToString() => Value;
}

// Domain events are immutable records
public sealed record AccountCreated(
    Guid Id, DateTimeOffset OccurredOn, AccountId Source, string Owner, decimal InitialBalance
) : IInternalDomainEvent, IInternalDomainEvent.IConstructionEvent
{
    string IDomainEvent.Source => Source.Value;
    IReadOnlyDictionary<string, string> IDomainEvent.Metadata => new Dictionary<string, string>();
}

public sealed record MoneyDeposited(
    Guid Id, DateTimeOffset OccurredOn, AccountId Source, decimal Amount
) : IInternalDomainEvent
{
    string IDomainEvent.Source => Source.Value;
    IReadOnlyDictionary<string, string> IDomainEvent.Metadata => new Dictionary<string, string>();
}

// Event-sourced aggregate
public sealed class BankAccount : EsAggregateRoot<AccountId, IInternalDomainEvent>
{
    public BankAccount(AccountId id, string owner, decimal initialBalance)
    {
        Id = id;
        Apply(new AccountCreated(Guid.NewGuid(), DateTimeOffset.UtcNow, id, owner, initialBalance)); // R1
    }

    public BankAccount(IEnumerable<IInternalDomainEvent> events) : base(events) { } // event replay

    public string Owner { get; private set; } = string.Empty;
    public decimal Balance { get; private set; }

    public void Deposit(decimal amount)
    {
        if (amount <= 0)
            throw new InvalidOperationException("Deposit amount must be positive");
        Apply(new MoneyDeposited(Guid.NewGuid(), DateTimeOffset.UtcNow, Id, amount)); // R2
    }

    protected override void _When(IInternalDomainEvent @event)
    {
        switch (@event)
        {
            case AccountCreated created:
                Id = created.Source;
                Owner = created.Owner;
                Balance = created.InitialBalance;
                break;
            case MoneyDeposited deposited:
                Balance += deposited.Amount;
                break;
        }
    }

    protected override void _EnsureInvariant()
    {
        if (Balance < 0)
            throw new InvalidOperationException("Balance cannot be negative");
    }

    public override string GetCategory() => "account";
}

This is a trimmed version of the compile-verified BankAccount example; the full version (with a Money value object, withdraw/close, and the EsRepository round-trip) is in USAGE_EXAMPLES.md.


Features

Core Capabilities

  • Tactical DDD building blocks: IEntity<TId>, IValueObject, AggregateRoot<TId, TEvent>, domain events with Metadata for idempotency and distributed tracing
  • Event sourcing: EsAggregateRoot<TId, TEvent> enforcing R1/R2/R3 invariant rules via template method, with event replay and {category}-{id} stream naming
  • State sourcing: OutboxRepository persists aggregate state + events atomically (Transactional Outbox)
  • Repository bridge pattern: IRepository (domain abstraction) ↔ IRepositoryPeer (persistence SPI; the transaction boundary)
  • CQRS: ICommand / IQuery / IInquiry / IProjection with the CqrsOutput<T> fluent output API
  • Event reaction: IReactor<TInput> hierarchy — IProjector<TInput> maintains read models, INotifier<TInput> converts internal events to external (integration) events
  • External publishing: IExternalDomainEventPublisher<TEvent> out-port; repositories never publish — a separate Relay does (see examples/EventInfrastructure/)
  • System reconciliation: IReconciler<TContext, TReport> for maintenance jobs (cleanup, consistency checks)

Design Philosophy

  • 🚀 Async/await throughout: All I/O operations return Task<T>, never blocking
  • 🎯 Clean Architecture: Unidirectional dependencies (Common → Entity → UseCase → Cqrs), ports & adapters
  • 📦 Zero external dependencies: Only .NET BCL + uContract.NET (ecosystem dependency)
  • 🔒 Thread-safe: Concurrent collections, Lazy<T>, and snapshot patterns
  • 💎 Strongly typed: Generic variance (in TInput, out TOutput) and nullable reference types
  • 🧬 Modern C# idioms: Records for events/value objects, pattern matching for event handlers
  • 🧪 Highly tested: 543 tests passing, >90% coverage across all modules
  • 🤝 Semantic parity: ~99% parity with Java ezddd 6.0.1, upstream tracked per release

Modules

Five NuGet packages with a unidirectional dependency chain (Common → Entity → UseCase → Cqrs → Core):

NuGet Package Purpose Depends on
ezDDD.Common Foundation utilities (BiMap, JsonUtil, Converter)
ezDDD.Entity Core DDD patterns (entities, value objects, aggregates, domain events) Common, uContract
ezDDD.UseCase Use cases, repositories (event/state sourcing), event infrastructure Entity
ezDDD.Cqrs CQRS patterns (commands, queries, projections, CqrsOutput) UseCase
ezDDD.Core All-in-one aggregator (no code of its own) All of the above

Note: Package IDs use the ezDDD.* prefix; namespaces use EzDdd.* (e.g. using EzDdd.Entity;).


API Overview

📖 Complete Documentation: API_REFERENCE.md — signatures, parameters, exceptions, and examples for every public API

Module Key Types
Common BiMap<TKey, TValue>, JsonUtil, Converter<TSource, TTarget>
Entity IEntity<TId>, IValueObject, IDomainEvent, IInternalDomainEvent, AggregateRoot<TId, TEvent>, EsAggregateRoot<TId, TEvent>, DomainEventTypeMapper
UseCase IUseCase<TInput, TOutput>, IReactor<TInput>, IReconciler<TContext, TReport>, IRepository<TAggregate, TId, TEvent>, IRepositoryPeer<TData, TId>, EsRepository<TAggregate, TId>, OutboxRepository<TAggregate, TData, TId>, IExternalDomainEventPublisher<TEvent>, ExitCode
Cqrs ICommand<TInput, TOutput>, IQuery<TInput, TOutput>, IInquiry<TInput, TOutput>, IProjection<TInput, TOutput>, IProjector<TInput>, INotifier<TInput>, IArchive<TData, TId>, CqrsOutput<T>

Examples

📚 More Examples: USAGE_EXAMPLES.md (30+ compile-verified scenarios)

  • Basic Patterns: Aggregates, value objects, domain events → USAGE_EXAMPLES.md
  • Event Sourcing: BankAccount aggregate, replay, R1/R2/R3 rules, EsRepositoryUSAGE_EXAMPLES.md
  • State Sourcing: Transactional Outbox, OutboxRepository, OutboxMapperUSAGE_EXAMPLES.md
  • CQRS: Commands, queries, projections, CqrsOutput fluent API → USAGE_EXAMPLES.md
  • System Reconciliation: Cleanup reconcilers, NullContext, scheduling → USAGE_EXAMPLES.md
  • Real-World Scenarios: Banking, e-commerce, inventory, order management → USAGE_EXAMPLES.md
  • Relay Pattern: Reference implementation of EventStoreRelay (event store → publisher) → examples/EventInfrastructure/

Requirements

  • .NET 8.0 or later (C# 12, nullable reference types enabled)
  • uContract 1.0.0+ — Design by Contract support (same ecosystem as Java ezddd's uContract); used by EsAggregateRoot invariant checking
  • No other dependencies — production code uses only .NET built-in APIs (System.Text.Json, System.Reflection, System.Collections.Concurrent)

Differences from Java Version

ezDDD.NET maintains ~99% semantic parity with Java ezddd 6.0.1 — core patterns (aggregates, R1/R2/R3 rules, repository bridge, Transactional Outbox, CQRS separation, reactor hierarchy) behave identically — while adopting .NET platform idioms.

Syntax and Platform Differences

Aspect Java ezddd C# ezDDD.NET
Async Synchronous execute(input) ExecuteAsync(input) returns Task<T>, non-blocking
Method naming camelCase() PascalCase() + Async suffix for async methods
Protected overrides when(), ensureInvariant() _When(), _EnsureInvariant() (underscore prefix)
Null safety Optional<T>, @Nullable Nullable reference types (T?), compiler-enforced
Generics <ID, E> <TId, TEvent> with variance (in / out)
Event handling instanceof chains Pattern matching with switch
Immutability final fields, getters record types with init properties
Serialization Jackson System.Text.Json (built-in)

API Mapping Highlights

Java ezddd C# ezDDD.NET
Optional<T> findById(ID) Task<T?> FindByIdAsync(TId)
addDomainEvent(E) / getDomainEvents() _AddDomainEvent(TEvent) / GetDomainEvents()
CqrsOutput.create().succeed() CqrsOutput<T>.Create().Succeed()
MessageProducer (moved to ezddd-gateway in 6.0.0) Excluded from core, matching Java; a .NET Gateway package is deferred post-1.0

Example Comparison

Java:

public class BankAccount extends EsAggregateRoot<AccountId, InternalDomainEvent> {
    private Money balance;

    public void deposit(Money amount) {
        var event = new MoneyDeposited(UUID.randomUUID(), Instant.now(), id, amount);
        apply(event);
    }

    @Override
    protected void when(InternalDomainEvent event) {
        if (event instanceof MoneyDeposited deposited) {
            this.balance = balance.add(deposited.amount());
        }
    }
}

C#:

public sealed class BankAccount : EsAggregateRoot<AccountId, IInternalDomainEvent>
{
    private Money _balance = new(0);

    public void Deposit(Money amount)
    {
        var @event = new MoneyDeposited(Guid.NewGuid(), DateTimeOffset.UtcNow, Id, amount);
        Apply(@event);
    }

    protected override void _When(IInternalDomainEvent @event)
    {
        switch (@event)
        {
            case MoneyDeposited deposited:
                _balance = _balance.Add(deposited.Amount);
                break;
        }
    }
}

Migrating from Java

See MIGRATION_GUIDE.md for side-by-side Java/C# comparisons of every pattern, syntax mapping tables, and common gotchas.


Documentation

User Documentation

  • 📖 API_REFERENCE.md - Complete API reference
    • Every public type and method with signatures, exceptions, and examples
    • Verified against the shipped public API baseline
  • 📚 USAGE_EXAMPLES.md - Real-world examples
    • 30+ compile-verified scenarios (banking, e-commerce, inventory, order management)
    • Event sourcing, state sourcing, CQRS, and reconciliation walkthroughs
  • 🔄 MIGRATION_GUIDE.md - Java → .NET migration guide
  • 📝 CHANGELOG.md - Release history (Keep a Changelog / SemVer)

Developer Documentation

  • 👨‍💻 AGENTS.md - Development standards and workflow (TDD, Tidy First, build/test commands)
  • 📋 docs/adr/ - Architecture Decision Records documenting design rationale
  • 🗺️ ROADMAP.md - Current status and post-1.0 considerations

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for development guidelines.

Before contributing:

  1. Read the Architecture Decision Records to understand design rationale
  2. Follow the standards in AGENTS.md — tests first (TDD), >90% coverage

License

MIT License — Copyright (c) 2025-2026 ezDDD.NET Contributors. See LICENSE for details.

This project is a derivative work of the Java ezddd library by Teddy Chen and contributors, which is licensed under the Apache License 2.0. See THIRD-PARTY-NOTICES.txt for the required attribution and license text.


References

Original Java Version (6.0.1)

This .NET port is based on Java ezddd 6.0.1 (GitLab commit: 3aac0f5; synchronized 2.1.0 → 4.1.0 → 6.0.1 before first publication)

Theory


Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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 (2)

Showing the top 2 NuGet packages that depend on ezDDD.Entity:

Package Downloads
ezDDD.UseCase

Use cases and repository patterns for ezDDD.NET - Clean Architecture support with event sourcing and state sourcing. Features: • IUseCase<TInput, TOutput> - Command pattern interface • IRepository/IRepositoryPeer - Bridge pattern for persistence abstraction • EsRepository - Generic event sourcing repository with reflection optimization • OutboxRepository - State sourcing with Transactional Outbox pattern • IReactor/IExternalDomainEventPublisher - Event reaction and external publishing ports • DomainEventMapper - Event serialization/deserialization • Transaction boundary enforcement • Async/await throughout Implements Clean Architecture layers with tactical DDD patterns.

ezDDD.Core

All-in-one package for ezDDD.NET - Tactical Domain-Driven Design patterns, CQRS, Event Sourcing, State Sourcing, and Clean Architecture for .NET 8+. This package references all 4 core modules: • ezDDD.Common - Foundation utilities (BiMap, Converter, JsonUtil) • ezDDD.Entity - DDD tactical patterns (Entity, AggregateRoot, EsAggregateRoot, DomainEvent) • ezDDD.UseCase - Use cases and repositories (IUseCase, EsRepository, OutboxRepository, IReactor) • ezDDD.Cqrs - CQRS patterns (Command, Query, Projection, Projector, Archive) Features: • Event sourcing with R1/R2/R3 correctness rules • State sourcing with Transactional Outbox • Bridge pattern (IRepository ↔ IRepositoryPeer) • Template method pattern (EsAggregateRoot) • Transactional Outbox relay for event publishing • Clean Architecture layers • Async/await throughout • Nullable reference types • Record types for immutable events • Pattern matching for event handlers • Zero external dependencies (only .NET BCL + uContract.NET) ~99% semantic parity with Java ezddd 6.0.1. Perfect for DDD practitioners using C# and .NET.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 127 7/6/2026