read.mir0n.pro

An environment for Domain-Driven Design

Domain-Driven Design makes one claim. A model built with the business and written into the code — not into a separate document — remains useful as the business keeps changing. A good model lasts.

The method tells you what to build. It does not tell you where to build it: identity, permissions, an audit trail, storage, messaging, screens — the environment you need before you can model anything.

DDD cannot run on air. Esquire is the ground: an application framework for backoffice systems — not a DDD framework, but an environment for one.


A bit of theory: what DDD asks of you

DDD's premise: in software with complex business rules, the hard part is not the technology. It is understanding the business. You build a model of the business with the people who know it. You name everything in one shared language. You write that model into the code. The names in the code are the names the business uses. When the business renames something, you rename it in the code that day.

The method has two halves. The strategic half concerns boundaries and language: where one model ends, where another begins, and how the two communicate. The tactical half is the pattern catalog: entities, value objects, aggregates, repositories, factories, domain services, and domain events. The tactical half is the best known. A project can follow every pattern in it and still have no model at all. Most DDD failures happen this way.

It also imposes limits.

DDD method limits

  1. No single company-wide data model. A bounded context is "a description of a boundary (typically a subsystem, or the work of a particular team) within which a particular model is defined and applicable" [2], so each context keeps its own "Customer", and the duplication is deliberate.
  2. The aggregate is a consistency boundary: "Within an aggregate boundary, apply consistency rules synchronously. Across boundaries, handle updates asynchronously" [2]. The working rules are Reference Other Aggregates By Identity and Use Eventual Consistency Outside the Boundary [5].
  3. Design Small Aggregates [5], or they become a lock everybody waits on.
  4. The model must not know about storage. No ORM detail, no SQL inside the business rules.
  5. Behavior lives with the data. Push it all into services and you get the anemic domain model [3].
  6. Language discipline is permanent: one language "used by all team members within a bounded context" [2].
  7. Not every part deserves it: "not all parts of the design are going to be equally refined. Priorities must be set" [2]. Sophisticated models "seldom turn out useful except when developed through an iterative process of refactoring, including close involvement of the domain experts" [2].
  8. It needs a business expert available continuously. Without one, you get the team's jargon.

Those limits have a price. The most expensive is this: everything outside the core domain still has to be built, and most of it earns nothing. The following answers that — the generic subdomains, how an entity is declared, the visibility constraint, where the core domain goes, and what else the environment covers.


The generic subdomains are already built

A generic subdomain is a part every business needs and no business competes on.

The advice about everything that is not the core is blunt: "Identify cohesive subdomains that are not the motivation for your project... give their continuing development lower priority than the core domain, and avoid assigning your core developers to the tasks... Also consider off-the-shelf solutions or published models for these generic subdomains" [2].

Count the subdomains in a business system: identity and sign-in, permissions, the audit trail, storage, messaging between services, metrics, logs, traces. Every one has to be built and running before the first business rule can.

Esquire supplies those parts already built and connected: a) a Keycloak-backed identity flow that keeps the user directory in sync, b) permissions resolved from the organization tree, c) an audit trail for every write, d) a messaging bus with pluggable transports, e) an observability stack that can be enabled or disabled. So, the motto — business logic and nothing else is left to write — in DDD language: once the generic subdomains are off-the-shelf, what is left is the distilled core, where the deep model and the supple design still have to be found.

How an entity is declared

DDD wants one language, used by everyone, written down in one place. In Esquire, an entity declares itself once, across five things at the same time:

An entity declares Which means DDD analogue
its datathe fields it holds, per kind (the entity's type)Entities [1] [2]
its permissionswho may see it, who may change it, who may change it only on their own record—
its behaviorwhich commands it accepts, and which handler runs for each eventDomain Events [2]
its validationwhat a value must satisfy — required, range, allowed valuesSpecification [1]
its look and feelthe tab it appears on, its order, its label, its hint, and how the value is rendered—

Permissions and the look and feel are outside DDD's concern — on purpose, so the model can stay clear of details. In practice, every piece of data must be restricted and displayed. Esquire declares both for each field in the entity dictionary, so the server and the frontend cannot disagree.

One entity dictionary, one statement: the server obeys, the REST contract exposes, the frontend builds the screen.

As a result, two gains for DDD:

1) The ubiquitous language gets a machine-readable home. In most projects, the shared language lives on a wiki page that goes stale in a month. Here it is the dictionary: the business label, the hint, the allowed values with their business names, the rule a value must satisfy. It cannot go stale: an update changes the system.

A model change is one edit. Add a field, drop one, relabel it, widen its values, tighten its rule — the server, the API, and the screen follow. The server and the frontend speak the same "ubiquitous" language.

2) The rich-versus-anemic coin lands on its edge. Fowler [3] describes the two models: a rich one puts the rules on the objects; an anemic one, in services. The dictionary is neither: it is metadata, the rules stated once, and the server and the frontend both read them from it.

The visibility constraint

Permissions — access rights — are outside DDD's concern. Visibility is part of them: every list must be limited to what a user may see. DDD has a term for a rule that must always hold — an "invariant" (for example, an account balance that never drops below zero). The visibility rule is one of those.

In DDD, invariants belong to the aggregate: "Define properties and invariants for the aggregate as a whole and give enforcement responsibility to the root or some designated framework mechanism" [2].

In Esquire, that rule lives on the entity tree. Your DDD model keeps the entity structure; that anchors visibility. Offices, users, accounts — each is a node, and each carries its path. A user sees their own subtree, nothing above it and nothing beside it. Roles decide what a user may do. Position decides over whom.

In most systems, the rule lives in every developer's memory, one query at a time. In Esquire, the rule is one parameter. The caller brings a position; a scoped lookup returns only matching entities. The framework's own reads cannot skip it — without it, the code does not compile. Writes start with the same lookup: find the entity, then act on it by id. What the caller cannot find, the caller cannot change. The scoped lookup is the designated framework mechanism.

I compared how open-source projects enforce visibility based on the org chart [6]. Tree or flat, the model made no difference. What mattered was whether the visibility rule applies itself. No project checks that the rule is in place while the first models and queries are written — in DDD words, while the invariants are defined. That is modeling work, and it is done once.

The model's structure, its aggregates, its types — all stay yours. One constraint: every entity has a position in the tree, stored as its path.

Where the core domain goes

Your domain brings its own entity kinds, and they sit on the same tree as everything else. The dictionary declares their fields, their sub-entities, and the screens for the operations they support. Your service carries out those operations through the REST contract and the bus.

Esquire ships a domain implementation prototype: pacMan, a Primitive ACcount MANager. It is a full vertical slice — tables, service, REST, and screens in the same administrative tool — with three operations: deposit, withdrawal, transfer, and nothing else. It shows how a domain attaches. It is not your domain.

Your domain implementation can take any shape. It can be a suite of microservices or one solid monolith. The framework neither writes it nor limits it.

pacMan is deliberately simple and written procedurally: a transaction service reads the account via a scoped lookup, checks the rules, and writes — nothing else. The transfer mimics a saga with no recovery phase. The debit and credit legs are single operations, each atomic on its own account, with nothing synchronizing them. The debit goes first and carries every check. The credit follows and needs none: adding money breaks no rule — in the hope that nothing crashes on the wire.

The dictionary defines an entity; your domain code decides what happens to it. They meet on the tree. Most of what you bring to Esquire is definition, not code: the entity structure, the dictionary with its rules and behavior, the bus topology, the metrics you watch. The code is your domain logic, ordinary code in your own repository.

Esquire is not a low-code tool, not a library suite, and not a generator. It is the environment where your business logic stands.


What else the environment covers

Many of Esquire's features map to DDD's building blocks [2]:

Entity. A tree node, described in the dictionary — metadata, not a rich type, so no compiler checks it. A rich type gets you compile-time checking. A dictionary entry gets you one description that the server, the API, and the frontend all obey, and it changes without a code update.

Domain event. In most projects, "publish a domain event" is a month of work: an outbox, a broker, ordering, retries, tracing. Here the bus is already in place, with ordering rules, a sequence number that determines which update event wins, and a trace that follows the message from one service to the next. All a business operation does is save the entity; the event goes out after the commit, and the bus does the rest.

Aggregate. An entity with its sub-parts — person, addresses, custom parameters, children — is loaded and saved as one, and a change number decides whose change wins. In DDD, you decide where an aggregate ends and defend that line in every review. On the tree, the boundary is structural: an entity has a parent, a path, and children, and its sub-parts move with it. Your own aggregates are still yours to define.

Aggregate root. The tree node. Kind plus id is the only handle the outside gets.

Repository. One self-contained SQL per method, no dynamic SQL, ORM types never in the service layer.

Shared kernel. The common and messaging modules. Esquire is shared code. Like any framework or library, it is not part of your core domain, and it sits inside every service.

Evans's advice for what is shared: "Keep this kernel small" [2]. In practice, shared code grows: a new Spring Boot version can break every service that uses it. Esquire is no different: adding one method to its messaging module forced every service image to be rebuilt.

CQRS read model [4]. The split between reads and writes is the baseline position. It is not a cache added only for speed. It is the tree, shaped for the way it is traversed, loaded from canonical tables, fed by entity events, and rebuilt from the source in case of drift.

DDD draws the blueprint; Esquire has parts built.


The neighbors

Tools labeled "DDD framework" exist. What they supply falls into three groups:

Each one makes life easier for a team practicing DDD; each implements a building block or two.

All three sit inside the domain implementation. Esquire sits around it: identity, permissions, audit, messaging, storage, and the screen.

A framework can support DDD. It cannot do DDD.


What a DDD practitioner gets

You keep your model and your patterns: your aggregates, your language, your context boundaries.

Identity, permissions, audit, messaging, and storage come built and connected.

A model change is one edit: the server, the API, and the screen follow.

Visibility is a position, carried into every read.

The DDD building blocks are working: events, aggregates, repositories, the read model.

DDD tells you what to build. Esquire is where you build it.


References

[1] Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software, Addison-Wesley, 2003 — the Specification pattern and the building blocks.

[2] Eric Evans, Domain-Driven Design Reference, 2015 — definitions and summaries of the DDD patterns.

[3] Martin Fowler, AnemicDomainModel, 2003 — why a model with no behavior fails.

[4] Martin Fowler, CQRS, 2011 — Command Query Responsibility Segregation: reads split from writes.

[5] Vaughn Vernon, Effective Aggregate Design, 2011 — four rules for sizing and linking aggregates.

[6] A missing filter looks exactly like a correct query — seven projects that scope visibility by the org chart.


The system is Esquire, a framework for backoffice systems — Java and Spring Boot, an Angular frontend, Postgres or Oracle, identity synchronized to Keycloak. The codebase is public at github.com/mir0n-pro. A deployment is running at esquire.mir0n.pro — sign in with mainadmin / q. It is a demonstration tree. The data is reseeded from time to time.