Skip to content
back to the episode
Episode summary2022Fellow

Learning Domain-Driven Design — Chapter 2, Part 2

The third session on Vlad Khononov's book — without Vladimir Ivanov, whom the club congratulates on his birthday, but with Alina from T-Bank's Collection team. Ilya, Vova Chistyakov, and Alexander go through the architectural and communication patterns: layered architecture and ports-and-adapters, CQRS, model translation, the outbox, sagas, and process managers.

Code of Architecture · Book Club8 min read

This summary is shorter than the full episode; examples and assessments reflect the participants’ experience and views.

The main thread of the material
01

Layers, ports and adapters, CQRS

Alina opens the chapter on architectural patterns: business logic matters, but so does how it is organised, and the first answer is layered architecture — presentation, service layer, business logic, and data access, where storage is not only a database but also S3. Vova recalls his first job: dao, services, and controllers packages, Spring with XML configuration — and the certainty that every backend is written that way. Alexander notes that Vlad starts with three layers and adds the service layer only when needed — when logic on Active Record requires transactionality across several objects; with Transaction Script the layer degenerates. Two clarifications matter: layers are logical boundaries inside one runtime while tiers are physical ones with their own lifecycle; dependencies run top-down, so the business layer depends on infrastructure, which makes a domain model hard to implement on it. Ilya brings up tools like ArchUnit and jQAssistant, and Alexander his talk on evolutionary architecture and an example of enforcing such conventions with a linter in Elixir.

Ports and adapters invert the dependency: business logic declares ports — interfaces — and infrastructure, which now absorbs presentation and data access, implements them with adapters. Alexander stresses that this follows directly from the dependency inversion principle and that for Vlad hexagonal, onion, and clean architecture are the same thing; the service layer is called the application layer here, and the participants like that the author spells out the synonyms rather than piling up a "sandwich" of terms. Then CQRS: polyglot modelling — one shape of data for search in Elasticsearch, another for analytics — and read models built on top of events for each task. The command execution model is strictly consistent, read models are eventually consistent, and Vlad advises starting with synchronous projections and checkpoints, adding asynchronous ones only when scale demands: otherwise you dive head first into distributed systems without a reason. The red line through all three sessions, in Vova's words, is "there are many tools, pick the simplest", and the pattern is chosen per bounded context, not for the whole organisation.

02

Model translation and integrating aggregates

Alexander recaps the strategic patterns — partnership, shared kernel, customer–supplier — and explains that in the communication chapter Vlad folds the anti-corruption layer and the open host into one topic: translating models between contexts. Ilya walks through the options: stateless — a proxy that translates on the fly and can carry versioning, even on a cloud API gateway; stateful — an aggregator that batches several requests and almost always needs storage, a database or a cache. Alina adds that stateful translation can run on streaming such as Kafka Streams or on batches in Spark — a direct continuation of Kleppmann's chapters. Alexander shows his own MECE tree of options: synchronous or asynchronous, stateless or stateful, inbound or outbound data — and admits it explained what bothered him about proposals to "put a BFF on top of the API gateway": without clear criteria the boundaries of applicability blur.

Integrating aggregates starts with publishing domain events correctly, and Vlad honestly walks through the typical mistakes. Publishing from the aggregate before the commit: the event is out, the transaction rolls back. Publishing from the application layer after the commit: the service may crash between the write and the send, and nobody learns about the change. Ilya has seen a variation — a home-grown transaction manager that hoarded events until the commit — with the same flaw. The answer is the outbox: events are written in the same transaction, a separate relay reads and publishes them, marking the sent ones; in the worst case you get duplicates and at-least-once semantics, which the club already knows how to live with. Vova notes the NoSQL variant — a slice of the outbox inside the document itself — and that publishing can be push or pull. Alexander recalls how one of the advertising platform's systems took events off the database transaction log — what Vlad calls log tailing.

03

Sagas, process managers, and what comes next

A saga is needed when a business transaction spans several aggregates — Alexander's example is creating a user, a sandbox, attaching a payment, and a subscription. There is no real transaction: a component listens to one aggregate's events and issues commands to others, retrying or running a compensating transaction on failure, such as refunding money already charged. Long-running here means a chain of separate transactions, not time, and the saga guarantees only eventual consistency: the system moves towards convergence or rolls back, freezing in an intermediate state on the way. Frequent sagas just to synchronise aggregates signal badly drawn boundaries, and Vova likes Vlad's phrasing that a saga can be used to check context boundaries. It is an expensive tool for money-related processes where the user cannot be left to sort things out with support; Alexander recalls colleagues from Avito describing their saga implementation.

Alina describes the process manager as a saga on steroids: a saga is nailed to events, while a process manager is a state machine with if/else logic that decides the next step itself. The book's example is a business trip: the manager proposes the cheapest flight, the employee may decline, then a supervisor's approval is needed, then a hotel is searched, and if there is none the whole trip is cancelled. A listener wrote in that a saga is usually treated as orchestrated first of all; Alexander replies that in Vlad's rendering the saga is closer to choreography and the process manager to an orchestrator, and advises comparing authors: tool boundaries are shaky, and different names often hide the same principles. On complex event processing he recalls Kleppmann: queries turned inside out — first you write the rule, then the stream of events flows through it, which suits fraud detection. The next session is about applying all these patterns in practice, with a bit of EventStorming.

Takeaways

What to take away

  1. 01Layered architecture suits Transaction Script and Active Record, but its top-down dependencies get in the way of a domain model; ports and adapters invert them, and for Vlad hexagonal, onion, and clean architecture are synonyms.
  2. 02CQRS starts with synchronous projections and checkpoints, adding asynchronous ones for real scale; the pattern is chosen per bounded context, not for the whole organisation.
  3. 03Publishing events before or right after the commit loses data or events; the outbox with a relay gives at-least-once, and log tailing takes events off the database log.
  4. 04Sagas and process managers give only eventual consistency through compensations; frequent sagas to synchronise aggregates are a signal to revisit the boundaries.

Sources