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:

  1. Describe what happens in the domain (events, commands, rules)
  2. Model who triggers what and what results are needed
  3. 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 clean
  • esdm 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.