Simple logic and the measure of the word "simple"
Vlad opens the tactical part by retelling Fowler's patterns, and the first is Transaction Script: the business layer is organised around procedures, each method fetches data, runs its steps, and commits the result, and the key requirement is atomicity — either everything reaches storage or everything rolls back. Alexander wrote such scripts early in his career and lays the traps out in order: an explicit transaction in a relational database is the easy case; a write to the database plus a log or a disk is already a distributed transaction; the most treacherous is the implicit one, when the whole system is not idempotent and a retried increment bumps the counter by an unknown amount. Vova adds that reading this chapter brings back the whole of Kleppmann's book — latency, unsynchronised clocks, select for update. The script is fine for supporting and generic subdomains and for integrating with a foreign solution inside an anti-corruption layer, and turns into an anti-pattern when core logic is built on it, with duplication and rules drifting apart.
Active Record is the same script for richer structures — objects and trees that are cheaper to assemble through an ORM. Vladimir immediately thinks of Spring Data REST with its database handles exposed, Vova of Django and its fondness for the pattern; it fits CRUD, reports, and small services, and the line past which everything gets rewritten as services keeps floating. The participants like that Vlad does not brand the anemic model an anti-pattern but calls it a tool: in the DDD community you are usually talked into "doing it properly", although for a simple part of the domain a simple solution is more honest. Alexander puts it pragmatically: do not swing the hammer where the domain is not the core — take Active Record, attach a front end, ship it, and forget it, because the core will demand something else anyway. Ilya adds that DDD is a business tool, and delivering value the simple way is the right choice; Vladimir suggests counting how many times the word "simple" was said in ten minutes.
The domain model: language, value objects, aggregates
Complex logic is where the domain model proper begins — Alexander traces the story from Fowler's book, where Evans is only mentioned, to Evans's own. The signs of a complex domain: many state transitions, invariants that must hold on every transition, and a competitive advantage that is hard to copy. Hence the split of complexity into essential, belonging to the domain, and accidental, which we invent ourselves; to avoid breeding the second, business logic lives in plain objects, infrastructure is hidden, and the code keeps the nouns and verbs of the domain — the ubiquitous language. Vova recalls Austrian companies whose domain was written in German; never having learned it, he wrote an IntelliJ IDEA plugin that translated a selected word through the Yandex API. The advice is simple: pick the language the company speaks and keep all the code in it.
A value object is defined by its values and needs no identifier: a name, a phone number, an email as dedicated types carry their validation rules inside, so nobody hunts for where a string was already checked. Vova draws the line on himself: blue is always blue, but there is more than one Volodya Chistyakov in the world, so people need an identifier and an entity whose attributes change while identity is preserved — all the way to the ship of Theseus. Alexander recalls a migration-service system that deduplicated people by full name, date of birth, and document history through fuzzy search, and four Andrey Ivanovs in his teams who deserved numbered football shirts. From the book: "use value objects whenever you can"; from practice: Kotlin inline classes as testing through typing. An aggregate is an entity with a consistency boundary: the hierarchy is updated in one transaction, the state is changed from outside only through the root, other aggregates are held by reference and converge over time. The book's example is a ticket reassigned when a support agent fails to answer in time; a chat question about entities in different stores resolves by the same logic — if a gap between them is acceptable, they are different aggregates.
Domain services, events, and Event Sourcing
The domain service is the third building block: a calculation that depends on several aggregates, such as an agent's response time given their schedule and tickets. Alexander likes how Vlad dismisses the objection that this breaks the "one aggregate, one transaction" rule: the service reads several aggregates and computes, but state is still changed one aggregate at a time. Vova notes there is no formal protection against updating two aggregates from such a service, only a convention, and Ilya adds domain events, which describe what happened to the aggregate and carry the data consumers need. A separate find of the chapter is managing complexity through degrees of freedom, as in physics: the more a system is allowed to do, the harder its behaviour is to predict, and invariants in aggregates narrow the state space and keep data from breaking inexplicably. Then comes the dimension of time: Vova retells Kleppmann's idea that a database is only a snapshot while the event stream is the source of truth, and the event-sourced model keeps the same blocks but stores state as a sequence of domain events.
The upsides are familiar: time travel when debugging, new projections on top of events that already happened, and audit. Vlad suggests keeping the aggregate version right in the model, and Vladimir came to appreciate that on a conflict you can fetch the events that arrived and decide whether to proceed. The downsides are a steep learning curve and event-schema evolution, which has a separate and, the participants joke, unfinished book of its own. From the questions section: performance is cured with snapshots that the tail of events catches up from, partitioning goes by aggregate identifier; deletion on a regulator's demand is handled with a forgettable payload, where sensitive fields are encrypted separately and the key is destroyed, or with events that carry only identifiers. To "why not write the log and the state in parallel" the answer is Kleppmann's: two tables are two sources of truth, someone will update only one, and CDC shows that fields changed but not why. Next: architectural patterns — and the book, Alexander notes, reads twice as fast as Kleppmann.
What to take away
- 01Transaction Script and Active Record are an honest choice for supporting and generic logic; their traps are distributed and implicit transactions, non-idempotent retries, and duplicated rules once the core is built on them.
- 02The anemic model is a tool, not an anti-pattern: for a simple part of the domain a simple solution is more honest, and the full domain model should be saved for the core.
- 03A value object is defined by its values and needs no identifier, an entity keeps its identity through change, and an aggregate is a consistency boundary changed from outside only through its root.
- 04Event Sourcing makes the event stream the source of truth and gives audit and new projections, but demands snapshots, schema evolution, and a separate plan for deleting personal data.