ESDM – Event-Sourced Domain Modeling
Website: https://www.esdm.io/
Core idea: Domain-first modeling language (YAML) that captures DDD, CQRS, and Event Sourcing building blocks in a machine- and human-readable format.
The Core Message
Talk about domain first — technology is secondary.
Common mistake: teams jump to “we’ll use Kafka / PostgreSQL / microservices” before defining what the system actually does. ESDM flips this:
- Describe what happens in the domain (events, commands, rules)
- Model who triggers what and what results are needed
- Only then decide on tech stack
This makes conversations with domain experts productive — they don’t need to understand Kafka to participate.
Why This Matters for AI
ESDM YAML is intentionally simple so that LLMs can read and write it directly.
Practical workflows this enables:
- Give an LLM your ESDM model → ask it to generate boilerplate (handlers, projections, API contracts)
- Give an LLM a feature description → ask it to draft new ESDM entities
- Use the model as a shared vocabulary between AI tools and human developers
The more your codebase is described in explicit domain concepts (not just folder names and class hierarchies), the more effectively AI can assist.
Building Blocks
ESDM captures the full DDD/CQRS/ES vocabulary. Every element is a separate YAML file with kind:.
| Kind | Role | Analogy |
|---|---|---|
domain |
Top-level grouping | Company / product name |
bounded-context |
Consistent vocabulary zone | Team / subdomain |
aggregate |
Consistency boundary — accepts Commands, produces Events | The “thing” that enforces rules |
command |
Intent to change state | User action / API call |
event |
Immutable fact that happened | Append-only history entry |
read-model |
Query-optimized projection of events | Denormalized view / DTO |
query |
Exposes a read model | GET endpoint |
process-manager |
Coordinates workflows across aggregates | Saga / state machine |
policy |
Business rule triggered by an event | “When X happens, do Y” |
value-object |
Immutable data, no identity | Money, Address |
domain-service |
Logic that doesn’t belong to one aggregate | Price calculator |
context-mapping |
Relationship between bounded contexts | Anti-corruption layer |
Example: Library Domain
The write-read loop in ESDM — a full working model in 7 files:
1. Domain
apiVersion: schema.esdm.io/core/v1
kind: domain
name: library
2. Bounded Context
kind: bounded-context
name: cataloging
scope:
domain: library
3. Aggregate — the consistency boundary
kind: aggregate
name: book
scope:
domain: library
boundedContext: cataloging
identifiedBy:
source: state
field: isbn # natural key — ISBN identifies a book
state:
type: object
properties:
title: { type: string }
author: { type: string }
isbn: { type: string }
required: [title, author, isbn]
4. Command — expresses intent
kind: command
name: acquire
scope:
domain: library
boundedContext: cataloging
aggregate: book # targets exactly one aggregate
data:
type: object
properties:
title: { type: string }
author: { type: string }
isbn: { type: string }
required: [title, author, isbn]
publishes:
- acquired # which event(s) this command produces
5. Event — immutable fact
kind: event
name: acquired
scope:
domain: library
boundedContext: cataloging
aggregate: book
data:
type: object
properties:
title: { type: string }
author: { type: string }
isbn: { type: string }
required: [title, author, isbn]
metadata:
annotations:
cloudevents.type: io.eventsourcingdb.library.book-acquired
6. Read Model — projection for queries
kind: read-model
name: books
scope:
domain: library
boundedContext: cataloging
paradigm: tabular
projections:
- boundedContext: cataloging
aggregate: book
event: acquired
rule: Append a row with `title`, `author`, and `isbn`.
7. Query — exposes the read model
kind: query
name: list-books
scope:
domain: library
boundedContext: cataloging
readModel: books
Flow: acquire command → book aggregate enforces rules → acquired event emitted → books read model updated → list-books query returns result.
Aggregate Rules: When NOT to Use One
Aggregates enforce invariants (rules that must hold). But not every consistency problem belongs there:
| Situation | Use instead |
|---|---|
| Rule spans multiple aggregates | Dynamic Consistency Boundary |
| React to events without owning state | Process Manager |
| Just need to expose data for queries | Read Model |
Forcing everything into Aggregates produces models that are “hard to reason about a year later” (ESDM docs).
Tooling
# Validate your model
./esdm lint
# Interactive visualization of the architecture
./esdm view
esdm lint— checks all YAML files for rule violations, exits 0 if cleanesdm view— renders an interactive graph of domains, contexts, aggregates, events, commands and their relationships- CLI available as pre-built binaries for macOS, Linux, Windows
- Free including commercial use
Relation to DDD Blue Book
ESDM is built on Eric Evans’ Domain-Driven Design building blocks but adds:
- Event Sourcing as first-class citizen (events are the source of truth, not current state)
- CQRS separation (commands write via aggregates, queries read via read models)
- Domain Storytelling extension — actor/work-object/sentence notation for workshop-style modeling
- Given-When-Then extension — feature/scenario specs that live alongside the model
Takeaway
ESDM gives a shared, formal language for:
- Talking to domain experts (no tech jargon needed)
- Generating code via AI (structured YAML = clear input)
- Documenting existing systems (reverse-engineer into the model)
- Enforcing consistency (linter = fitness function for the domain model)
Start with the domain story. Let technology follow.