Coada / writing

Introducing Moment and Facet: Your Domain Model Deserves to Run Before Your Code Does

You changed a field. Three downstream services broke in production. Nobody saw it coming — not the type checker, not the tests, not the code review. The contract between your service and theirs was never written down, never tested, and never visible to anyone. It lived in tribal knowledge and hope. And it broke on a Tuesday.

If you've built distributed systems, you have a version of this story. Maybe it was a renamed event field. Maybe it was a new required property that one consumer expected and another didn't. Maybe it was a multi-step process that spanned three services and silently stopped completing because the second one changed its response shape. The details differ. The shape is always the same: the failure lived in the space between services, where nobody was looking.

I've been building software for over twenty years — Fortune 500 companies, startups that didn't survive, solo consulting where the architecture lived entirely in my head. Across all of that, this is the failure mode that never goes away. Not because engineers are careless. Because there's no tool that makes the contracts between services visible, testable, and enforceable before you ship.

I've spent the last year writing about this gap. I named patterns. I documented a methodology. I wrote about what happens when you use AI not for code generation, but for domain modeling — the hard, slow, unglamorous work of deciding what your system means before deciding how it runs.

What I didn't write about is that I was building the thing I couldn't find.

Today I'm releasing Moment — an open-source domain specification toolchain — and Facet, its companion visualization platform.


The gap that nobody names

Here's the thing about Domain-Driven Design that nobody talks about at conferences: there's no specification layer.

You whiteboard it. You sticky-note it. You argue about bounded context boundaries in a room (or alone, staring at a screen). And then you go write code. There's nothing in between. No artifact that says "this is the model" in a way that generates your types, your tests, your documentation, and your API contracts. No way to ask "does this event flow actually work?" before you've implemented both sides.

Your domain model lives in seven places by the end of week one: the wiki page that's already stale, the TypeScript interfaces that diverged on Tuesday, the tests that test the wrong contract, the Markdown doc nobody reads, the API spec that's three fields behind, the whiteboard photo from last sprint, and someone's head.

Six of those will be wrong by next month. You just won't know which six.

Evans gave us the vocabulary for where things live — bounded contexts, aggregates, entities, value objects. That spatial model changed everything about how we think about software. But the vocabulary for how things move through time — events crossing boundaries, processes orchestrating across contexts, policies reacting to state changes — has no formal home. It lives in conversations that ended months ago and assumptions nobody wrote down.

That's the gap. And it's where every painful production incident I've ever debugged actually lived.


Two tools. One workflow.

Moment lives in your terminal. Facet lives in your browser. Together they do something that doesn't exist anywhere else in the DDD ecosystem: they let you run your domain model before you write implementation code.

Moment is a specification language and toolchain. You write a .moment file — one file — that describes your bounded contexts, aggregates, commands, events, and the temporal flows between them. Flows are the key. A flow describes events crossing bounded context boundaries with explicit relationship types and contracts. OrderPlaced crosses from Ordering to Fulfillment via CustomerSupplier, and the contract says orderId and items are required. That crossing has never had a home before. Now it does.

From that one file, Moment generates everything downstream: typed TypeScript interfaces, BDD scenarios, test scaffolds with crossing assertions, specification documents with Mermaid diagrams, and AsyncAPI 3.0 contracts. One specification, six artifact types, zero drift. Deterministic output — same input, same output, clean git diffs.

But generation isn't the revelation. Simulation is.

Facet is where your domain model becomes visible. Run moment simulate and Moment produces synthetic event flows — complete with causation chains, correlation tracking, and branch paths. Open Facet and you see those flows rendered as interactive timelines. You watch OrderPlaced cross from Ordering to Fulfillment. You see the causation chain — which event caused which. You see where a contract field is missing, where a process stalls, where a branch path leads somewhere your model didn't anticipate.

You see your domain model running. Before a single aggregate class exists.

I can't overstate what this felt like the first time it worked. I'd spent months writing specifications, generating code, trusting that the model was right because the types compiled and the tests passed. Then I opened Facet and watched the events flow through the system I'd described — and I could see the places where my thinking was incomplete. Not wrong, exactly. Incomplete. Assumptions I'd made about how contexts interact that I'd never been forced to make explicit.

In every other engineering discipline, you test the design before you build. Civil engineers don't pour concrete and then check load calculations. Chip designers don't fabricate and then verify logic. But in domain modeling, we've been going from whiteboard to code for twenty years and pretending that's fine.

It's not fine. We just didn't have the tooling to do better.


What the specification looks like

A .moment file reads like a domain description, not a programming language:

context "Ordering" [Core]

  aggregate "Order"
    identity orderId: UUID

    command PlaceOrder
      input customerId: UUID, items: OrderItem[]
      precondition orderNotPlaced: "Order has not already been placed"
      emits OrderPlaced

    event OrderPlaced
      orderId: UUID
      customerId: UUID
      items: OrderItem[]
      placedAt: DateTime

flow "order-placed"
  lane ordering "Ordering" [Core]
  lane fulfillment "Fulfillment" [Supporting]

  moment "Order submission"
    ordering: PlaceOrder
    ordering: OrderPlaced crosses-to fulfillment via CustomerSupplier
      contract
        orderId: UUID [required]
        items: OrderItem[] [required]

The flow block is what makes this different from every DDD tool that's come before. It encodes the temporal dimension — the thing Evans didn't formalize, the thing Brandolini's sticky notes gesture at but can't enforce. The crossing contract that broke on a Tuesday? This is where it lives now.

I won't walk through the full pipeline here — the site has a seven-tab interactive example showing the generated TypeScript, Gherkin, test scaffolds, spec docs, AsyncAPI, and simulation output from a single .moment file. Go look. It's the thing I'm proudest of on that page.


Where this came from

I'm a solo founder. No team. No co-founder. Just me and an AI partner doing the kind of knowledge crunching that Evans described in 2003 — except instead of a room full of domain experts, it's one architect with twenty years of scar tissue and a language model that never forgets a bounded context boundary.

That practice produced Signal-Driven Development — a three-pass convergence methodology where you model, run a gap report, resolve gaps, and iterate until the gap count hits zero. Zero gaps is the definition of done that DDD never had. I wrote about it. I built the templates. I applied it to every product in my ecosystem, across hundreds of ADRs and thousands of domain decisions.

And through all of it, one realization kept growing: the spatial model isn't enough. DDD gives you a world-class vocabulary for structure — what lives where, what owns what, what depends on what. But it gives you almost nothing for motion — how events flow through that structure over time, what contracts govern the crossings, what happens when a process spans three contexts and the second one fails.

That's what Moment formalizes. And Facet makes it visible.


The ecosystem

Moment and Facet are part of a larger toolchain called Complai. The full workflow:

Sift discovers domains — bounded contexts, aggregates, commands, events — through AI-mediated knowledge crunching. It publishes structured domain events.

Moment takes those building blocks and adds temporal scope — flows, crossings, and contracts. It generates typed implementations and simulation scenarios.

Facet visualizes those scenarios — interactive timelines, causation chains, crossing contracts, branch paths. It's where you see your model run.

Forge (coming soon) bootstraps project structure from Moment's typed artifacts.

Every event flowing between these tools uses a shared envelope format. Each tool can be used independently — Moment doesn't require Sift, Facet doesn't require an account for local simulation. The full ecosystem page shows how they connect.


Current state

Moment is functionally complete. Ten packages published on npm under @mmmnt/*. The full pipeline works end-to-end: parse, derive, generate, emit. Schema governance, drift detection, simulation, MCP server — all shipped.

Facet is live at facet.mmmnt.dev.


Try it

git clone https://github.com/mmmnt/mmmnt.git
cd mmmnt && pnpm install && pnpm turbo build
moment parse spec.moment
moment derive
moment generate --all
moment simulate

Then open the simulation output in Facet and watch your domain model run.


Moment stands on the shoulders of the DDD community — Evans, Young, Brandolini, Vernon, Tune, Khononov — and encodes their patterns into a toolchain that enforces what books can only recommend. And a thanks to Hatoum for NDD, and introducing me to DDD 11 years ago.

I built the thing I needed. I think you might need it too.