Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

One Pattern, One Page

A walkthrough of the @frenchexdev/ddd-* TypeScript corpus, one page per pattern. Each page treats the pattern as a triplet: the runtime decorator that reifies the concept, the analyzer that enforces its usage rules, and the codegen that emits the typed scaffolding around it. The triplet is the monorepo's signature — every DDD pattern is open/closed-parameterised by a source generator, strictly additive, and dog-fooded by the CV site itself.

This series is the TypeScript counterpart to the C#-centric Domain-Driven-Design & Code Generation article and the From a Big Ball of Mud to DDD migration field guide. Where those treat DDD as a modelling discipline applied to a legacy monolith, this one treats it as a catalogue of typed building blocks you can import individually.


The triplet thesis

Every canonical pattern in the corpus ships as three coordinated packages.

The runtime (ddd-X) exposes the decorator and the metadata types it stamps onto a class. It does not implement business logic; it reifies a label that downstream tools can read. A class without the decorator is just a class; a class with @AggregateRoot({ name: 'Subscription' }) is a class plus a typed registry entry that the rest of the corpus can find, lint and generate code for.

The analyzer (ddd-X-analyzer) is the static lint pass. It walks the AST after compilation and asserts the usage rules that the runtime alone cannot express: that an @AggregateRoot does not depend on another aggregate, that an @ACL is the only thing allowed to import from a foreign bounded context, that a @DomainEvent is immutable. The analyzer emits diagnostics with stable codes that compliance reports consume.

The codegen (ddd-X-codegen) is the source generator. It reads the decorator metadata from the registry and emits typed validators, factory shells, registries indexed by name, and compliance hooks. Generators are strictly additive — they write only under outDir, never mutate sources — and run inside a virtual filesystem committed once at fixpoint, never incrementally between stages.

How it is built: the analyzer and codegen members are themselves source-generated. Each pattern's *-analyzer and *-codegen package owns a tiny spec.ts that calls defineAnalyzerSpec or defineCodegenSpec from @frenchexdev/ddd-spec-features/codegen. The ts-codegen-pipeline reads the spec and emits diagnostic codes, rule modules, analyzer entry points, test skeletons, property-test harnesses, registry templates and per-pattern summary modules — committed at fixpoint into src/generated/. Writing a new pattern is therefore: write the runtime decorator, write two specs, write the hand-shaped template functions the codegen pass invokes. The rest is generated.


Part 01: Subdomain

@Subdomain reifies Vernon's Core / Supporting / Generic classification. The pattern that lets you say, in the type system, that the billing engine is the strategic core while the notification queue is generic and outsourceable.

Part 02: Bounded Context

@BoundedContext is the linguistic and behavioural boundary. Same word, different meanings — the decorator says which meaning lives where.

Part 03: Context Relationship

@ContextRelationship types the cross-context power dynamic — conformist, customer/supplier, partnership, published-language, separate-ways. The map you draw on the whiteboard, made compilable.

Part 04: Anti-Corruption Layer

@ACL reifies the translation layer between contexts so foreign types never leak into your domain.

Part 05: Shared Kernel

@SharedKernel is the minimal cross-team contract with explicit participants. The narrowest possible shared surface, made narrow on purpose.

Part 06: Module

@Module is the organisational unit below the context: how you carve a context into navigable pieces without dropping back to file-system folklore.

Part 07: Port

@Port is the domain-owned interface — Hexagonal at the type level. The contract the domain demands; the adapter delivers.

Part 08: Adapter

@Adapter is the infrastructure-side implementation of a Port. Conformance suites, not duck typing.

Part 09: Domain Event

@DomainEvent is the immutable past fact emitted by an Aggregate. The integration backbone of the corpus.

Part 10: Event Bus

The multi-subscriber broadcaster for @DomainEvent instances. Where the integration backbone actually flows.

Part 11: Outbox

Reliable event dispatch via the Outbox pattern — write the aggregate and the event in the same transaction, dispatch asynchronously.

Part 12: Event Sourcing

The decorator that flips an aggregate from state-based to event-stream-based, rebuilt on demand from its event log.

Part 13: Event Store

The port for appending and replaying event streams, with memory / Postgres / Kafka adapters that satisfy the same conformance suite.

Part 14: Snapshot

The decorator that marks a serialisable cut of aggregate state, used to short-circuit long event streams.

Part 15: Snapshot Store

The port for saving and loading those cuts, with memory and Postgres adapters.

Part 16: Saga

The long-running orchestration aggregate. State machine, correlation id, compensations.

Part 17: Saga Store

The port that persists in-flight saga state, with memory / Postgres / Redis adapters.

Part 18: Projection

The decorator that turns an event stream into a read-optimised view, recomputable at will.

Part 19: Storage

The generic blob/object port and its filesystem / S3 adapters.

The full-text search port and its Meilisearch adapter.

Part 21: Cache

The cache port and its memory / Redis adapters — typed by key and value shape.

Part 22: Log

The structured logging port and its console adapter. Logs are typed events, not free-form strings.

Part 23: Metrics

The metrics port and its Prometheus adapter. Typed counters, gauges, histograms.

Part 24: Tracing

The distributed tracing port and its OpenTelemetry adapter.

Part 25: Notification

The outbound notification port and its email / WebSocket adapters.

Part 26: Audit Trail

The compliance-oriented audit log and its Postgres adapter.

Part 27: RBAC

The authorization port (role/permission) and its static adapter.

Part 28: i18n

The internationalisation port and its ICU adapter.


Part 29: Kernel

ddd-core — the kernel types every other package depends on: Id<T>, Result<T, E>, Guard, the ok / err / map / mapErr combinators.

Part 30: Value Object

@ValueObject reifies "equal by structure, immutable, no identity". Money, EmailAddress, SubscriptionPeriod.

Part 31: Entity

@Entity reifies "identity continuity over time". A Customer is the same Customer across two snapshots of state.

Part 32: Identity Strategy

The decorator that pins the id-generation strategy per aggregate — sequence, UUIDv7, branded string.

Part 33: Aggregate Root

@AggregateRoot is the consistency boundary plus the optimistic lock anchor. The only entry point into its internal cluster.

Part 34: Factory

@Factory encapsulates complex construction so invariants hold at birth.

Part 35: Domain Service

@DomainService is the stateless domain operation that does not belong to a single entity.

Part 36: Behavior

@Behavior is the reusable cross-entity behaviour mixin — the named slice of policy.

Part 37: Design by Contract

ddd-dbc reifies Meyer's pre / post / invariant on methods so violations fail at call time.

Part 38: Schema Validation

@Schema decouples shape validation from the runtime; the Zod adapter is one possible backend.

Part 39: Specification

@Specification is the reified business predicate. AND / OR / NOT composition; testable in isolation.

Part 40: Repository

@Repository returns aggregates, never DTOs. RepositoryStyle lets you pick collection-oriented vs persistence-oriented semantics.

Part 41: Use Case

@UseCase reifies the user-goal level operation, à la Cockburn / Clean Architecture — a name the product team can recognise on the call site.

Part 42: Application Service

@ApplicationService is the transactional orchestrator. Loads aggregates, calls them, persists them.

Part 43: CQRS Messages

@Command and @Query reify the message contracts that flow through the mediator.

Part 44: CQRS Handlers

@CommandHandler and @QueryHandler are the single-dispatch handlers wired by Mediator.

Part 45: Mediator

The dispatch primitive — Request<TResponse> resolves to exactly one handler.

Part 46: Pipeline Behavior

MediatR-style cross-cutting wrap-arounds for handlers: validation, logging, transactions.

Part 47: Read Model

@ReadModel is the DTO returned by @QueryHandler — distinct from the aggregate, optimised for the read side.

Part 48: Config Module

The typed cross-cutting configuration unit. No process.env peppered through the domain.

Part 49: CLI Task

The typed operational task — domain commands exposed at the application boundary as named CLI verbs.


Part 50: CLI Framework

ddd-cli — the framework that hosts the plugin set and exposes the ddd command.

Part 51: CLI Kernel

ddd-cli-core — the primitives every CLI plugin shares: argument parsing, plugin registration, output formatting.

Part 52: CLI Plugin — Scaffold

The plugin that emits skeleton packages for a new pattern triplet.

Part 53: CLI Plugin — Codegen

The plugin that drives the codegen pass across the workspace.

Part 54: CLI Plugin — Compliance

The plugin that produces the feature-to-test traceability matrix.

Part 55: CLI Plugin — Spec

The plugin that runs the contract / specification system at the workspace level.

Part 56: CLI Plugin — BC

The plugin focused on bounded-context-level operations: listing, validating, drawing the context map.

Part 57: CLI Plugin — DB

The plugin for database-side operations driven by the corpus metadata: schema diff, migration scaffolding.

Part 58: Preset

ddd-preset bundles curated sets of patterns + analyzer rules so a new package opts into a coherent slice of the corpus in one line.

Part 59: Testing

ddd-testing — the test helpers consumed by every package's *.test.ts: contract fixtures, builder helpers, fake adapters.

Part 60: Spec Kernel

ddd-spec and ddd-spec-features — the internal contract system supporting every pattern. The thing every other triplet quietly depends on.


How to Read This Series

If you know DDD and want the tour: read the index, then dip into any individual part. Each page stands alone.

If you are new to DDD: read Domain-Driven-Design & Code Generation first for the vocabulary, then start at Part 01: Subdomain and go in order.

If you are migrating a legacy codebase: read From a Big Ball of Mud to DDD first for the migration field guide (C# but transferable), then come back here to pick the TypeScript building blocks you need.

If you are implementing a specific pattern: jump straight to its part. Every page is self-contained — it states what the pattern reifies, what the runtime exports, what the analyzer enforces, and what the codegen emits.


Prerequisites

  • TypeScript 5.4+ with decorators enabled (the corpus uses standard TC39 decorators, not the legacy experimentalDecorators flag).
  • Working knowledge of the DDD vocabulary — aggregate, bounded context, value object, domain event. The companion C# article covers the basics if needed.
  • Comfort reading decorator-based class metadata. The corpus does not use reflection at runtime; it stamps typed metadata onto classes that the codegen reads at build time.
  • A monorepo mindset. The corpus is pnpm-workspace-shaped — every pattern is a separate package and you wire them by adding dependencies, not by importing from a god-package.

A note on the spec-first discipline

Every triplet member follows the same shape on disk:

  • src/spec.ts — the single declarative spec. Names the pattern, the parent requirement, the feature, the acceptance criteria, and either the rules (for analyzers) or the templates (for codegens). Read this first when exploring a package.
  • src/templates/*.ts (codegen only) — the hand-written render functions invoked by the pipeline. Pure, banner-wrapped, fixture-tested.
  • src/pipeline.ts — wires the generators from @frenchexdev/ddd-spec-features/codegen against the spec and runs runFixpoint from @frenchexdev/ts-codegen-pipeline.
  • src/generated/ — the committed output of the fixpoint pass. Never edited by hand.
  • src/_local-features.ts — the dog-fooded @FeatureTest/@Verifies declarations that bind the package's tests to the same feature ids the analyzer spec produces.

Articles in this series open each pattern by reading its spec.ts, because that is where the contract lives. The generated artefacts only show up in code snippets when the shape is the point.

⬇ Download