ezDDD.Entity
1.0.0
dotnet add package ezDDD.Entity --version 1.0.0
NuGet\Install-Package ezDDD.Entity -Version 1.0.0
<PackageReference Include="ezDDD.Entity" Version="1.0.0" />
<PackageVersion Include="ezDDD.Entity" Version="1.0.0" />
<PackageReference Include="ezDDD.Entity" />
paket add ezDDD.Entity --version 1.0.0
#r "nuget: ezDDD.Entity, 1.0.0"
#:package ezDDD.Entity@1.0.0
#addin nuget:?package=ezDDD.Entity&version=1.0.0
#tool nuget:?package=ezDDD.Entity&version=1.0.0
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.
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
- Features
- Modules
- API Overview - Key types (Complete API Docs →)
- Examples - Minimal example (30+ More Examples →)
- Requirements
- Differences from Java Version
- Documentation
- Contributing
- License
- References
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 withMetadatafor 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:
OutboxRepositorypersists aggregate state + events atomically (Transactional Outbox) - ✅ Repository bridge pattern:
IRepository(domain abstraction) ↔IRepositoryPeer(persistence SPI; the transaction boundary) - ✅ CQRS:
ICommand/IQuery/IInquiry/IProjectionwith theCqrsOutput<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 useEzDdd.*(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,
EsRepository→ USAGE_EXAMPLES.md - State Sourcing: Transactional Outbox,
OutboxRepository,OutboxMapper→ USAGE_EXAMPLES.md - CQRS: Commands, queries, projections,
CqrsOutputfluent 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
EsAggregateRootinvariant 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:
- Read the Architecture Decision Records to understand design rationale
- 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)
- Repository: Java ezddd (GitLab) by Teddy Chen (TeddySoft)
- Ecosystem: uContract.NET - Design by Contract dependency
Theory
- Domain-Driven Design - Eric Evans, tactical DDD patterns
- Event Sourcing - Martin Fowler
- CQRS - Greg Young
- Clean Architecture - Robert C. Martin
| Product | Versions 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. |
-
net8.0
- ezDDD.Common (>= 1.0.0)
- uContract (>= 1.0.0)
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 |
See https://github.com/cwouyang/ezDDD.NET/blob/master/CHANGELOG.md for release notes.