Temp.DDDToolkit.FluentValidation
3.2.0-preview.6
See the version list below for details.
dotnet add package Temp.DDDToolkit.FluentValidation --version 3.2.0-preview.6
NuGet\Install-Package Temp.DDDToolkit.FluentValidation -Version 3.2.0-preview.6
<PackageReference Include="Temp.DDDToolkit.FluentValidation" Version="3.2.0-preview.6" />
<PackageVersion Include="Temp.DDDToolkit.FluentValidation" Version="3.2.0-preview.6" />
<PackageReference Include="Temp.DDDToolkit.FluentValidation" />
paket add Temp.DDDToolkit.FluentValidation --version 3.2.0-preview.6
#r "nuget: Temp.DDDToolkit.FluentValidation, 3.2.0-preview.6"
#:package Temp.DDDToolkit.FluentValidation@3.2.0-preview.6
#addin nuget:?package=Temp.DDDToolkit.FluentValidation&version=3.2.0-preview.6&prerelease
#tool nuget:?package=Temp.DDDToolkit.FluentValidation&version=3.2.0-preview.6&prerelease
DDDToolkit
Source generators that remove the repetitive parts of domain driven design in .NET. You declare the intent with an attribute; the generator writes the base type, the equality members, the identifier plumbing, the persistence mapping and the API conversions.
Documentation: dylansnel.github.io/DDDToolkit, the
docs/ folder as a site.
[AggregateRoot<Guid>("ORD")]
public partial class Order
{
public Order(OrderId id, CustomerId customer) : base(id)
{
Customer = customer;
RaiseDomainEvent(new OrderPlaced(id, customer));
}
public CustomerId Customer { get; private set; }
public partial IReadOnlyList<OrderLine> Lines { get; }
public void AddLine(OrderLine line) => _lines.Add(line);
}
That is the whole declaration. OrderId did not have to be written at all: naming the raw value
generates it as an allocation-free identifier with parsing, comparison and JSON support. Order gets
its base class, an optimistic concurrency version, a domain event list only it can write to, and a
_lines backing field that Entity Framework maps directly while the outside world sees a read-only
list.
Declare the identifier yourself when other aggregates, DTOs or API contracts refer to it, which gives it a file of its own to navigate to:
[EntityId<Guid>("ORD")]
public readonly partial record struct OrderId;
[AggregateRoot<OrderId>]
public partial class Order { }
Documentation
| Page | What it covers |
|---|---|
| Getting started | One module built step by step: an id, a value object, an aggregate, a test, a database, a second module |
| What the generator writes | The generated code for one small aggregate, file by file, and why it is generated |
| Identifiers | [EntityId<T>], struct versus record ids, parsing, prefixes |
| Value objects | [ValueObject], [SingleValueObject<T>], validation and the always-valid twin |
| Entities and aggregates | [Entity<T>], [AggregateRoot<T>], child entities, read-only collections, referencing by id |
| Invariants | Named IInvariant<T> rules and the CheckInvariants() seam, the two stages, and the save that runs them |
| Domain events | Declaring, raising and draining events, and their stable names |
| Designing aggregates | The four rules of aggregate design, and how the toolkit holds each one |
| Testing | The aggregate testing kit: acting on an aggregate and asserting on what it raised |
| Failure handling | Validating without exceptions, and when to throw anyway |
| Entity Framework | Wiring, the generated converters, mapping, concurrency and migrations |
| Composite keys | [KeyPart]: keying a table on more than the id, and carrying that into every owned table |
| Delivering domain events | In-process dispatch or the outbox, and how to choose |
| Row level security | Running a context's queries as the caller, and row access rules written in C# as Postgres policies |
| Supabase | Exporting each module's migrations for supabase db push, as part of the build |
| Start-up checks | What the registrations check before the host serves anything, run with one call, in a fixed order, and turned off by name |
| Modules | DDD_Module, [assembly: Module] and the boundary the analyzer checks |
| Module contracts | What a module publishes, why, and where to keep it |
| Access requirements | What a command or a query requires of its caller, the checks that hold it to that before the handler, and the behavior written for Mediator |
| How a request is checked | One request followed from the attribute on it to the rows it touches: the generated requirement, the module's checks, the statement the access check sends, the caller on the connection and the policies, with a walk-through to click through |
| Integration events | Contracts between modules, the outbox and the inbox, versioning |
| Transports | Carrying integration events out of the process: pgmq, Wolverine, MassTransit, or a sink of your own |
| Tenancy | The first supporting domain: what it is for, what it does and does not do, and how its pieces connect |
| Tenancy: the tutorial | Tenancy built into an application step by step: why each step, the whole files, and what they do |
| Tenancy: your module | The module that holds Tenancy: its packages, the model, your own classes and ids, the context and the registration, and calling its managers |
| Tenancy: roles and permissions | The permission catalogue, the starter roles a new tenant gets and how they follow your changes, who may give a role, and containment |
| Tenancy: who may do what | Who acts and Tenancy's queries about the caller, what a request requires of its caller, and what a module reads of Tenancy |
| Tenancy: keeping tenants apart | The query filter and the save check that keep every read and save in the caller's tenant, and system work |
| Tenancy: names and languages | Turning the ids a module answers into names, how a seat is shown, and refusals and role names in the reader's language |
| Tenancy: invitations | Inviting a person without an account yet into a tenant, with a unit and a role waiting |
| Tenancy: operators and history | The application's operators across tenants, who wrote and changed each row, and the access history |
| Tenancy on Postgres | Tenancy's rules as row level security policies and functions in Postgres, how to set them up, and what they stop |
| Tenancy: the sample | The Tenancy sample: what is in it, the people and units of its seed, and its parts |
| Tenancy sample: who may do what | The sample's modules in layers, a request from route to save, what each request declares, roles and crews, paging, operators and invitations |
| Tenancy sample: GraphQL | One schema over the sample's three modules, what a query costs, the rules each module's schema keeps, and the administration's gateway |
| Tenancy sample: try it | Running the sample, its UI, one call followed to its refusal, and the presets that each break one rule |
| Tenancy sample: design choices | Every Tenancy design choice with its code, something to try in the sample, and the test that holds it |
| Tenancy: design and limits | Why Tenancy is shaped the way it is, what it does not do, and upgrading from key to permission |
| Tenancy API: aggregates and entities | Generated from the code: the templates of tenant, organization, unit, seat, role and invitation, with their value objects, invariants and events |
| Tenancy API: managers | Generated from the code: TenantManager, OrganizationManager, SeatManager, RoleManager and InvitationManager, the stores a storage package implements, and the registration a host calls |
| Tenancy API: catalogue and starter roles | Generated from the code: permissions, the catalogue, starter roles and their sync |
| Tenancy API: access manager and users | Generated from the code: TenancyAccessManager, the Tenancy user, actors, tenant selection and the requirement attributes, [Operator] among them |
| Tenancy API: queries and row access | Generated from the code: db.Tenancy() and its queries, the read model, the projection and the row access rules |
| Tenancy API: refusals | Generated from the code: every refusal code and what it means |
| Tenancy API: Entity Framework | Generated from the code: the public API of the Entity Framework package, with [OfTenant] and the mapping of a context |
| Tenancy API: Postgres | Generated from the code: the public API of the Postgres package |
| Membership | The second supporting domain: access to one thing through the people on it, what it does and does not do, how its pieces connect, and what the application decides |
| Membership: the tutorial | Membership built into the Harbor application step by step: a board with its members, owner and roles, its database rules, and reach from above |
| Membership: resources and members | The packages, a whole resource with members and no Tenancy, the member list written for you, names, what the member list keeps, and several kinds of resource |
| Membership: permissions and roles | The settings of a permission, who holds one, who may give a role, and roles a customer makes: starter roles, changing a role, whose a role is |
| Membership: from the check to the save | Requirements that combine, what holds a change from the check to the save, MembershipAccessManager and the queries inside your own statements, system work, and the expert hold |
| Membership on Postgres | The functions your row access rules ask, who may ask them, the start-up check, and the lock on the member rows and the owner |
| Membership: beside an organization | Who a member is, where the roles come from and reach from above, with Tenancy or an organization of your own |
| Membership: design and limits | Why each piece of a resource with members is yours to write, and what the package does not do |
| Membership API: resource and members | Generated from the code: the member template and its member list, periods and roles held, what the resource's class says, and the custom role template |
| Membership API: rules and permissions | Generated from the code: MembershipRules, where the roles come from, the permissions on a resource and their settings, reach from above, and who a member is |
| Membership API: managers and access | Generated from the code: the member manager, the role manager, who may be a member, MembershipAccessManager, the requirements and the resource access contracts |
| Membership API: refusals | Generated from the code: every refusal code, the codes of one resource, and their texts |
| Membership API: Entity Framework | Generated from the code: the public API of the Entity Framework package |
| Membership API: Postgres | Generated from the code: the public API of the Postgres package |
| Writing your own supporting domain | Shipping a domain as a package that applications extend with their own ids and classes, without losing its rules |
| GraphQL | AddDDDToolkitTypes(), the generated scalar bindings, errors with codes, Relay node ids, one schema over the modules |
| FluentValidation | A value object's rules as a FluentValidation validator, and value objects inside a request validator |
| Localization | Validation errors and invariant violations in the reader's language, looked up by code |
| Performance | The benchmarks behind the struct-versus-record advice, including where they disagree with it |
| Diagnostics | Every DDD000xx diagnostic and how to fix it |
| Migrating to 3.0 | Every 2.x break, with the before and the after |
With an AI coding agent
An agent that has never seen the toolkit writes the base class the generator already writes, a settable collection, a sealed value object. Three things teach it otherwise.
A skill. skills/dddtoolkit holds the declarations, the rules the generators
enforce, the wiring for Entity Framework and modules, and the fix for every DDD diagnostic. It is an
Agent Skill, the format Claude Code, Codex, Cursor and GitHub Copilot read.
This repository is also a Claude Code plugin marketplace, so in Claude Code:
/plugin marketplace add DylanSnel/DDDToolkit
/plugin install dddtoolkit@dddtoolkit
For another agent, npx skills add DylanSnel/DDDToolkit installs it, or copy the folder into the
agent's skills directory.
The docs as text. The site serves llms.txt, an
index of the pages with what each covers, and
llms-full.txt, all of them in one file. Every
page is also plain Markdown at its own address with .md added, such as
docs/invariants.md.
The diagnostics. Every DDD diagnostic carries a link to its entry in Diagnostics, so an IDE opens it from the error list and an agent that reads the build output can follow it.
Packages
Reference DDDToolkit and add the integrations you actually use. Each integration package brings its
own generator, so referencing it is all the configuration there is.
For now 3.x is published under Temp. ids: Temp.DDDToolkit,
Temp.DDDToolkit.EntityFramework, and so on, each package in the table below with Temp. in
front of its name. The nuget.org account that owns the DDDToolkit.* ids cannot publish at the moment,
so DDDToolkit there is still 2.0.22. Only the package id is different. The assemblies and namespaces
are the ones this page names, so using DDDToolkit; stays as it is.
3.2.0 is in preview. This page and the docs describe it: 3.2.0-preview.6 as they are written. Until
3.2.0 is released, add every Temp.DDDToolkit.* package with --prerelease, or with
--version 3.2.0-preview.6, and keep them all at the same version. A package of this release next to 3.1.0
of another fails restore with NU1605, a package downgrade, or fails at run time with a MissingMethodException.
| Package | Use it for |
|---|---|
DDDToolkit |
Base types and the core generators. Start here. |
DDDToolkit.Abstractions |
The attributes and marker interfaces alone, for projects that must not reference the runtime. With DDDToolkit.Analyzers beside it the generators run there too, which is what a contracts project wants. |
DDDToolkit.EntityFramework |
Value converters, model conventions, domain event dispatch, invariant checks, optimistic concurrency, the outbox and the inbox. |
DDDToolkit.Messaging.Postgres |
A pgmq sink, so the outbox enqueues inside the same Postgres transaction that writes the aggregate, and a consumer that reads a queue into the modules' inboxes. |
DDDToolkit.Messaging.Wolverine |
Wolverine as the transport between one process's outbox and another's inbox. |
DDDToolkit.Messaging.MassTransit |
MassTransit 8 as that transport, for those already on it. |
DDDToolkit.EntityFramework.Postgres |
Row level security on any Postgres: each connection a context opens runs as the current user (ICurrentUser), as PostgREST's do, and [RowAccess] rules written in C# become the policies. |
DDDToolkit.EntityFramework.Supabase |
Your Entity Framework migrations and row access rules written as Supabase migration files by the build, so supabase db push and branching apply them, and a CI build that fails when one is missing. |
DDDToolkit.Auth.Supabase |
Supabase Auth's access tokens validated in any host, against the keys the project publishes or its JWT secret, and turned into the current user row level security runs your queries as. No Entity Framework needed. For server code, a client for Auth's admin API: invite an address, make a user under an id of your own, and find or delete one by id. |
DDDToolkit.Auth.Supabase.AspNetCore |
The same in ASP.NET Core: a JWT bearer scheme for Supabase Auth, and each request's user as the current user. |
DDDToolkit.Auth.Supabase.AzureFunctions |
The same in Azure Functions on the isolated worker: a worker middleware that makes each HTTP invocation's user the current user. |
DDDToolkit.Mediator |
One call that dispatches domain events through Mediator instead of a hand-written delegate. |
DDDToolkit.FluentValidation |
A generated validator per value object. |
DDDToolkit.Localization |
Validation errors and invariant violations phrased in the reader's language, through IStringLocalizer. |
DDDToolkit.HotChocolate |
GraphQL scalar bindings and converters for typed identifiers, failures as coded errors and as typed errors in mutation payloads, plus a subscription sink. Needs HotChocolate 16.6.9 or later. |
DDDToolkit.HotChocolate.Fusion.InMemory |
One GraphQL schema over a modular monolith: each module a source schema, composed by a Fusion gateway in the process. Needs HotChocolate Fusion 16.6.9 or later. |
DDDToolkit.NewtonSoft.Json |
Newtonsoft converters and a contract resolver that honours [Internal]. |
DDDToolkit.Supporting.Tenancy |
Tenancy, a supporting domain: tenants, their organization tree, seats and roles over a permission catalogue, a manager per aggregate, and the access manager. No database in it. |
DDDToolkit.Supporting.Tenancy.Abstractions |
Tenancy's markers for domain classes, [OfTenant] on the property that says which tenant a row belongs to, for a module's domain project that sees only Tenancy's ids. Tenancy's package references it. |
DDDToolkit.Supporting.Tenancy.EntityFramework |
Tenancy in a context of your own, with its queries as Entity Framework queries (db.Tenancy()), on every provider, and the convention that maps [OfTenant]. |
DDDToolkit.Supporting.Tenancy.Postgres |
Tenancy's queries as SQL functions, its row level security policies, and the start-up checks. |
DDDToolkit.Supporting.Membership |
Membership, a supporting domain: access to a resource through its members, the roles they hold for a period, and roles a customer makes. No database in it. Its generator writes the member list on your aggregate. |
DDDToolkit.Supporting.Membership.Abstractions |
Membership's markers for domain classes, [At] on the property that says where a resource hangs. Membership's package references it. |
DDDToolkit.Supporting.Membership.EntityFramework |
A resource's members in a context of your own, mapped by convention, a registration named after each resource, and queries inside your own statements (db.Boards.WhereIHold(permission)). Its generator joins a resource to Tenancy where an application has both. |
DDDToolkit.Supporting.Membership.Postgres |
A resource's access queries as SQL functions for your row access rules, and a start-up check. |
DDDToolkit.Testing |
The aggregate testing kit. A test-only reference; it brings no test framework of its own. |
dotnet add package Temp.DDDToolkit --prerelease
There are five more packages you never reference directly: DDDToolkit.Analyzers and the
.EntityFramework, .EntityFramework.Supabase, .FluentValidation and .HotChocolate analyzer
packages beside it. Each one
carries the generators for its integration and arrives as a dependency of the package above, so
adding DDDToolkit.HotChocolate is all it takes to get the GraphQL generator.
Requires .NET 10. The generators themselves target netstandard2.0 and carry no runtime
dependencies, so they load in any recent SDK.
The core has no mediator dependency and does not need one: in-process event delivery is a delegate you
write, and DDDToolkit.Mediator only saves you writing it. The examples publish through Mediator
rather than MediatR because MediatR is commercially licensed from version 13, and this repository
prefers dependencies its users can take for free. MediatR still works perfectly well with the toolkit;
Delivering domain events shows the delegate to write for it.
What the generators produce
| You write | You get |
|---|---|
[EntityId<T>] on a readonly partial record struct |
Value, a constructor, Empty/IsEmpty, Parse/TryParse, IParsable<T>, IComparable<T>, explicit conversions, a JSON converter, and for Guid a time-ordered Create(), which implements ICreatableEntityId<T>, and CreateUnique/CreateSequential |
[EntityId<T>] on a partial record |
A reference type identifier: Value, equality over it, Parse/TryParse, for Guid a time-ordered Create() (ICreatableEntityId<T>) and CreateUnique/CreateSequential, and a Valid twin. It has null rather than Empty, and no conversion operators |
[SingleValueObject<T>] on a partial record |
A wrapper with value equality, a Valid twin and validation |
[ValueObject] on a partial record, positional or with { get; protected init; } properties |
Structural equality across the properties you did not exclude, a With(...) for changed copies, and a Valid twin |
[Entity<TId>] / [AggregateRoot<TId>] on a partial class |
The base type, a persistence constructor, a CheckInvariants() seam, GetInvariantViolations() and EnsureInvariants() over it, over any nested IInvariant<T> rules and over every child entity it holds, an EnsureOwnInvariants() / GetOwnInvariantViolations() pair that stops at this object, and an implementation for every get-only partial collection property |
Add DDDToolkit.EntityFramework and the same declarations also produce value converters, [Owned]
and [ComplexType] annotations, and a single Add<Module>Converters call for your DbContext. Add
DDDToolkit.HotChocolate and they produce GraphQL type bindings. You never write that code.
Beyond the declarations
Three things the toolkit does that are not a generated member.
Invariants. State a rule in the generated partial void CheckInvariants()
seam, or as a nested IInvariant<T> when it deserves a name, a code and a test of its own. There are
two moments to ask. GetInvariantViolations() answers with a list and never throws, for the
application that wants to handle "not consistent yet"; EnsureInvariants() throws, and an interceptor
runs the same check before every SaveChanges that writes the entity, child entities included, so a
broken rule stops the save. Asking the aggregate root asks the whole aggregate, its children too, each
violation naming the entity that reported it, so a handler can act on an aggregate and ask what that
broke without a DbContext taking part. State nothing and the compiler erases the seam, so an
aggregate with no invariants pays nothing.
Integration events. The outbox writes one row per event in the same transaction as the aggregate. A sink delivers it afterwards: to another module in this process, to a pgmq queue, to a GraphQL subscription, or to one you wrote. Versioning and upcasting keep last year's payload readable, and an inbox keyed by message id and consumer makes the receiving side idempotent.
Modules. Declare a project's module with <DDD_Module>Ordering</DDD_Module>, once for a
folder of projects in a Directory.Build.props, or with [assembly: Module("Ordering")], and name what it
publishes with [ModuleContract]. An analyzer then reports where another module reaches past the
contract. It says nothing at all until both sides opt in.
Status
Version 3.0 is a breaking release. Identifiers may be structs, domain events moved from Entity to
AggregateRoot, IDomainEvent carries an id and a timestamp, aggregates gained an invariant seam and
a concurrency version, and misapplied attributes now report a diagnostic instead of silently
generating nothing.
Coming from 2.0.22, read Migrating to 3.0: it lists every break with the code you have and the code you need. The changelog has the rest.
License
MIT. See LICENSE.
| 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
- FluentValidation (>= 12.0.0)
- Temp.DDDToolkit (>= 3.2.0-preview.6)
- Temp.DDDToolkit.FluentValidation.Analyzers (>= 3.2.0-preview.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.2.0-preview.7 | 0 | 10/29/2026 |
| 3.2.0-preview.6 | 0 | 10/29/2026 |
| 3.2.0-preview.5 | 51 | 10/26/2026 |
| 3.2.0-preview.4 | 49 | 10/26/2026 |
| 3.2.0-preview.3 | 57 | 10/9/2026 |
| 3.2.0-preview.2 | 70 | 10/5/2026 |
| 3.2.0-preview.1 | 69 | 10/4/2026 |
| 3.1.0 | 98 | 10/1/2026 |
| 3.0.1 | 93 | 10/1/2026 |
| 3.0.0 | 100 | 9/27/2026 |