# ESDM 0.15.0: Last Call Before 1.0

Ten weeks ago we **[put ESDM under the MIT license](/resources/blog/esdm-is-now-open-source)** and asked you to tell us what works and what doesn't. **ESDM 0.15.0, released last week, is what came back.** It is a big release, and most of it traces to someone using ESDM on a real model and reporting what happened: a vocabulary that speaks more than one language, a command that turns a model into a browsable set of pages, a stable way to point at any element from outside the model, and a linter that explains every finding it makes.

Some of it will turn a clean model red, and we expect this to be the last release before 1.0. Both facts belong in the same sentence, because together they are the reason to read on: here is what changed, what breaks and how to fix it, and why the next few weeks are the moment to tell us where we got it wrong.

<!-- more -->

## Words in More Than One Language

The ubiquitous language of a Bounded Context is one term per concept, on purpose: the point of choosing one word is that there is one word. What that could not hold was a team that speaks German with its domain experts and writes English code. Such a team had to drop one of the two words, and in practice it dropped the one the experts say. **[That need](https://github.com/thenativeweb/esdm/issues/10)** was raised in July, and **[a proposal for a SKOS-based extension](https://github.com/thenativeweb/esdm/issues/45)** – SKOS being the W3C vocabulary for thesauri – named the three cases that matter: two words for one concept, a translation that quietly misleads, and a term that corresponds to a different term in the next context over.

We declined the proposal's shape, an extension with a second document per context, because the ubiquitous language is not something that sits beside the model – the model is written in it – and two documents for one vocabulary would drift apart. An export to SKOS remains possible later, as a projection of the model rather than its source. The three cases went straight into the design, as fields on the documents that already exist. A status per term, which the proposal also asked for, needs no field: a term in the model is the agreed one, and Git records who decided. **A Bounded Context now declares the `language` its vocabulary is written in**, and a term can carry `translations`: the same concept in another language, with its own definition and its own `avoid` list, so a translation that would mislead – `Kauf` for `Acquisition`, when donations are acquisitions too – is named and refused rather than tolerated.

```yaml
language: en
ubiquitousLanguage:
  - term: Acquisition
    definition: The process of adding a book to the catalog, whether bought or donated.
    translations:
      - language: de
        term: Erwerb
        definition: Die Aufnahme eines Buchs in den Katalog, ob gekauft oder gespendet.
        avoid:
          - term: Kauf
            reason: Auch Spenden sind ein Erwerb.
```

Within each language there is still exactly one term, so **the principle holds per language rather than being abandoned**. Where one context depends on another, the **[Context Mapping](https://www.esdm.io/concepts/context-mapping/)** between them names two roles, such as customer and supplier or upstream and downstream, and its new `terms` list records the correspondence by those roles: what Sales calls a Customer, Billing calls an Account. And `esdm glossary --language de` renders the **[whole glossary](https://www.esdm.io/getting-started/running-esdm-glossary/)** for the people who speak German, marking every term that has no translation yet, so the gaps stay visible instead of disappearing.

## A Page for Every Element, and an Address for Each

In early July, `esdm documentation` went on our list, and two reports from the same weeks show why. Someone wanted to **[link from a specification to individual elements](https://github.com/thenativeweb/esdm/issues/8)** of the model, and had resorted to one file per element so that a file path could serve as the address. Someone asked why **[`esdm glossary` on a full model produced an empty page](https://github.com/thenativeweb/esdm/issues/26)**. The glossary shows definitions, not names, and that model had no terms declared yet – but the question was reaching for a browsable list of the model's elements, and the closest thing to one was the transient output of `esdm view`. Both point at the same thing: **a rendering of the whole model that a person can browse and a document can point into.**

**[`esdm documentation`](https://www.esdm.io/getting-started/running-esdm-documentation/)** is the first half. It writes the model as a tree of Markdown pages, one per element, containers as directories with a `README.md`, everything cross-linked with relative links and ready to be committed next to the code or read on GitHub. It renders the same tree `esdm view` renders, so the two cannot disagree about where an element sits. The **[reference notation](https://www.esdm.io/reference/reference-notation/)** is the second half: a URI such as `esdm:domain=library/bounded-context=catalog/aggregate=book` that walks the model from the Domain inward and names the kind and the name at every step. **It is a name, not a location**, so it survives every file move, split, and merge, and the page for an element follows from its reference by a fixed rule: drop the `esdm:` prefix, replace each equals sign with an underscore, and end a container in `README.md`, so `esdm:domain=library/bounded-context=catalog/aggregate=book` is the page `domain_library/bounded-context_catalog/aggregate_book/README.md`.

## The Linter Explains Itself

ESDM's linter is opinionated, and one of its opinions **[was contested](https://github.com/thenativeweb/esdm/issues/27)** in July: an Event bound to an Aggregate should not repeat the Aggregate's name, so `instrument-cataloged` under `instrument` draws a warning. The rule is right – every name derived from the model, a class in code or an event type in EventSourcingDB, would otherwise double the Aggregate, as in `InstrumentInstrumentCataloged` – but the reason was written nowhere. The linter had a rule ID and a message, and no place a reader could go to learn what the rule protects.

0.15.0 closes that gap for every rule at once. The documentation gains **[Linter Rules](https://www.esdm.io/reference/linter-rules/)** reference pages, one entry per rule and per pipeline diagnostic – the errors raised before any rule runs – each saying what it checks and why. Every finding now ends in a `see` line that links to its entry, and the JSON output carries the same address as `documentationUrl`, so your tooling can point people there too. **An opinionated linter owes its reasons, and now it pays them.**

Where the tooling was wrong rather than unexplained, it is corrected. A field report from **[a model of about 175 documents](https://github.com/thenativeweb/esdm/issues/19)** described two name collisions that `esdm view` seemed to hide, and chasing them turned up more than the report could see: `esdm view` never rendered Entities or free-standing Events at all, and `esdm view <path>` showed only the first element matching the last segment rather than all of them. Both are fixed, and a Given-When-Then Feature now sits under the Aggregate, Dynamic Consistency Boundary, Process Manager, or Read Model it is about rather than under the Domain.

Three smaller corrections came the same way. `event-without-publisher` no longer fires when a Dynamic Consistency Boundary publishes an Aggregate's Event, because publisher resolution now lives in one place rather than three. Field references such as `identifiedBy.field` **[accept `userId` as well as `user-id`](https://github.com/thenativeweb/esdm/issues/52)**. And the update hint **[stays quiet](https://github.com/thenativeweb/esdm/issues/23)** when `esdm completion` runs from a shell profile, where it had greeted one user on every new terminal.

**If you run ESDM today, update to 0.15.0**, run `esdm lint`, and then try `esdm documentation --output docs` on your own model. The **[update guide](https://www.esdm.io/getting-started/updating-esdm/)** has the steps, and the next section has what to do if the linter comes back red.

## What Breaks, and What to Do About It

Three changes turn a model that is clean under 0.14.0 into one that fails under 0.15.0, and one new warning may appear. Each shows up in the output – the first two with a few follow-on errors from the schema validator that disappear once the cause is fixed – and each fix stays inside the model.

**A Bounded Context that declares a `ubiquitousLanguage` must now declare its `language`.** The linter reports `missing required field "language"` on the context. Add the language the terms are written in as a tag, `language: en` or `language: de` – BCP 47, the same tags the web uses – and the finding is gone. A context without terminology is unaffected.

**A Given-When-Then rejection must name an invariant.** The free-form `then.rejection.reason` is gone from the schema; every rejection is `{ invariant: <name> }`, so its cause is a rule the unit actually declares. A Scenario that used `reason` now fails validation, with `unknown field "reason"` among the errors. Replace it with the name of the invariant the Scenario protects, and add that invariant to the unit if it was never written down – which is usually what the free-form text was hiding. A new warning, `uncovered-invariant`, points the other way: on an Aggregate or Dynamic Consistency Boundary that has Features, an invariant no Scenario exercises is reported as untested.

**No two of the five domain types may share a name in one Bounded Context.** In code, Aggregates, Dynamic Consistency Boundaries, Entities, Value Objects, and Domain Services end up as the types of one module under their own names – nothing is composed into them the way an Aggregate is composed into the names of its Events – so an Aggregate and a Dynamic Consistency Boundary both called `loan` would collide there, and are now an error, `esdm/structure/ambiguous-name`, reported at the second definition with a note at the first. Rename one of them and its references. Every other pair may still share a name – an Aggregate and a Read Model both called `order` are one concept on the write and the read side – and the **[Bounded Context](https://www.esdm.io/concepts/bounded-context/#one-namespace-for-the-domain-types)** page draws the line.

One step touches no model but belongs here. **If your project has a `schemas/` directory** from `esdm add-schema`, the linter stops with `schemas-directory-drift` until you run `esdm update-schema`, because the schemas embedded in the new binary differ from the ones on disk, and the linter refuses to run with two sets of rules that disagree.

## Still 0.x

ESDM is a 0.x release, and the number means what it says: until 1.0, a release may still change the schema and the rules in ways that turn a clean model red. When we wrote in **[May](/resources/blog/one-year-of-eventsourcingdb-and-the-language-that-belongs-beside-it)** that ESDM's core language is stable, and in **[July](/resources/blog/esdm-is-now-open-source)** that the core had settled, that was a statement about pace – the schema stopped moving every week – not a promise that nothing would move before 1.0. Three things do move in 0.15.0, and the previous section says what to do about each. From 1.0 on, a different contract holds: a breaking change then means a new major of the `apiVersion`, and documents written against the old one keep validating. That is the mechanism the **[design principles](https://www.esdm.io/introduction/design-principles/)** describe, and 1.0 is where it takes over.

**We expect 0.15.0 to be the last release before 1.0.** That makes the coming weeks unusual: a change to the schema now costs you a line in a model, and after 1.0 it costs us a new `apiVersion`. If a shape in this release is wrong – if `translations` does not fit how your team actually works, if the reference notation lacks a kind you need, if a rule fires where it should not – this is the cheapest moment there will ever be to say so.

Feedback does not mean every request lands, and it should not. We said no to **[distributing ESDM through package managers](https://github.com/thenativeweb/esdm/issues/22)**, because a single binary you can pin per project and download into a CI job is a deliberate choice, and Homebrew plus its counterparts on Windows and Linux are more release work than we can carry right now. We said no to **[an OOUX extension](https://github.com/thenativeweb/esdm/issues/24)** – Object-Oriented UX, a method that structures user interfaces around domain objects – because ESDM describes the domain and a UI structure sits one layer further out. We said no to **[citation fields in the schema](https://github.com/thenativeweb/esdm/issues/9)**, because where a modeler's knowledge comes from is information about the model rather than about the domain.

Each answer is in the issue with its reasons, and each can be reopened by a case we have not seen. **A no with a reason is still an answer, and every request gets one.**

## Say It Before 1.0

Ten weeks of your feedback shaped this release more than any plan of ours did: the multilingual vocabulary, the reference notation, the rule pages, and most of the fixes each started as an issue about a real model. **The next few weeks decide what 1.0 looks like**, and that works the same way.

If you use ESDM, update and tell us what you find – **[open an issue on GitHub](https://github.com/thenativeweb/esdm/issues)** or write to **[hello@thenativeweb.io](mailto:hello@thenativeweb.io)**, including what worked better than you expected. If you have not tried ESDM yet, **[Your First Model](https://www.esdm.io/getting-started/your-first-model/)** takes you from install to a lint-clean model, and 0.15.0 is a good version to start with: it is the one 1.0 will be measured against.
