Domain-Driven Design (DDD) — подход к проектированию сложных систем, в котором предметная область (domain), а не технологии, задаёт границы и язык решения. Для системного аналитика в контексте распределённых систем DDD отвечает на три вопроса: где проходят границы подсистем и интеграций, какими терминами формулировать требования, и какие объекты должны изменяться атомарно. Статья даёт основы стратегического и тактического DDD на уровне понимания и спецификации — без инструкций по реализации на коде.
1. Суть темы
Domain-Driven Design предложен Эриком Эвансом (Domain-Driven Design: Tackling Complexity in the Heart of Software, 2003). Главная идея: сложность программы должна отражать сложность бизнеса, а не стека технологий. Если эксперты предметной области и команда разработки называют одни и те же вещи по-разному — требования и код расходятся с реальностью.
DDD делится на два уровня — их важно различать с самого начала:
| Уровень | Что включает | Зона ответственности аналитика |
|---|---|---|
| Стратегическое проектирование | Bounded Context, Ubiquitous Language, поддомены (Subdomain), Context Mapping, Anti-Corruption Layer | Границы scope, глоссарий, интеграции между частями системы |
| Тактическое проектирование | Entity, Value Object, Aggregate, Domain Event, Domain Service, Repository, Factory | Модель внутри контекста: инварианты, события, границы транзакций |
Стратегия определяет где живут понятия и как контексты связаны. Тактика — из чего состоит модель внутри одного контекста. В распределённой системе граница агрегата часто совпадает с границей локальной транзакции; граница Bounded Context — с границей автономной команды, контракта API или потока событий. Паттерны согласованности между контекстами (Saga, eventual consistency, CQRS) — в 03.06.01 Архитектурные паттерны распределённых систем.
Детали протоколов и топологии интеграции — 03.05.01 Виды интеграции (точка-точка, ESB, API-led) и 03.01.04 Интерфейсы и интеграционные взаимодействия; здесь фокус на границах модели и политике связей между контекстами.
2. Ключевые понятия
Bounded Context (ограниченный контекст)
Bounded Context — явная граница, внутри которой действует одна согласованная доменная модель и свой Ubiquitous Language. За пределами границы то же слово может означать другое — и это нормально.
Пример интернет-магазина — понятие «Заказ» в разных контекстах:
| Контекст | Что такое «Заказ» |
|---|---|
| Оформление заказа | Корзина, адрес доставки, способ оплаты |
| Логистика | Груз, вес, габариты, пункт отправления |
| Биллинг | Счёт, сумма, статус оплаты |
Объединять эти представления в одну «универсальную» модель — путь к монолитной модели, которую никто не понимает. Границы контекста связывайте с 02.01.04 Определение границ решения (Scope Modelling); на C4 container часто соответствует BC — 06.03.05 C4-модель.
Context Diagram ≠ Context Map. 03.01.02 Моделирование контекста системы (Context Diagram) показывает систему и внешних акторов — кто с системой взаимодействует. Context Map (ниже) описывает отношения между Bounded Contexts и политику моделей на их стыках. Обе диаграммы дополняют друг друга, но отвечают на разные вопросы.
Связь с микросервисами — эвристика, не правило. Один Bounded Context часто становится кандидатом на отдельный deployable unit, но в сложных системах один BC может состоять из нескольких сервисов, а несколько BC — жить в модульном монолите. Критерий для аналитика: граница контекста = граница Ubiquitous Language, автономной команды и независимо эволюционирующей модели, а не обязательно один pod в Kubernetes. Декомпозицию см. 03.01.03 Декомпозиция системы на подсистемы и модули.
Ubiquitous Language (всеобщий язык)
Ubiquitous Language — единый язык экспертов предметной области и команды разработки. Термины из бизнеса попадают в требования, глоссарий, диаграммы и код.
Типичный разрыв: бизнес говорит «акцепт претензии», а в спецификации — status_code = 3. UL устраняет это: в требованиях — Claim.accept(), в 02.04.02 Критерии приёмки (Acceptance Criteria) — «претензия в статусе Accepted», в 10.01.14 Шаблон глоссария и словаря данных — определение термина с указанием контекста. Словарь данных на уровне атрибутов — 03.03.03 Словарь данных (описание).
Поддомены (Subdomain): Core, Supporting, Generic
Внутри предметной области выделяют поддомены по конкурентной ценности — это декомпозиция проблемного поля, а не карта сервисов:
| Тип | Суть | Куда вкладывать моделирование |
|---|---|---|
| Core | Уникальное конкурентное преимущество | Глубокий UL, детальные агрегаты, совместное моделирование с экспертами |
| Supporting | Нужен бизнесу, но не отличает от конкурентов | Достаточная модель, без перфекционизма |
| Generic | Типовая функция (аутентификация, платёжный шлюз) | Покупка/интеграция готового решения |
Приоритизация глубины моделирования пересекается с 02.03.02 Приоритизация требований (MoSCoW, WSJF, Кано): Core-поддомен — зона «Must have» для качества модели, Generic — кандидат на готовый продукт.
Поддомен ≠ Bounded Context. Один поддомен может быть реализован несколькими BC (разные команды, разные модели одной области). Несколько поддоменов могут жить в одном BC, если команда и модель согласованы. Subdomain отвечает на вопрос «куда вкладывать усилия анализа»; BC — «где заканчивается одна модель и начинается другая». Не склеивайте «Core = отдельный сервис»: приоритет моделирования и граница scope в ADR — разные решения.
Context Mapping: типы связей и артефакт
Context Map — схема отношений между Bounded Contexts. Для аналитика это рабочий артефакт, а не декоративная диаграмма. Минимум, который фиксируется в ADR или приложении к спецификации интеграции:
| Поле артефакта | Что записать |
|---|---|
| Пара контекстов | Upstream → Downstream (кто поставляет модель/данные) |
| Тип связи | См. таблицу ниже |
| Контракт | REST/gRPC, события, файл — со ссылкой на спецификацию |
| ACL / перевод | Нужен ли, таблица соответствия терминов |
| Риски | Зависимость от чужой модели, версионирование, задержка согласованности |
Типовые отношения на Context Map:
| Тип связи | Суть | Когда применять | Что фиксировать |
|---|---|---|---|
| Partnership | Две команды совместно развивают связанные модели | Тесная координация, общий релизный цикл | Совместные контрактные решения, общий backlog изменений модели |
| Customer-Supplier | Upstream поставляет модель downstream по договорённости | Есть переговорная сила downstream | SLA на изменения контракта, приоритет запросов downstream |
| Conformist | Downstream принимает модель upstream без перевода | Upstream — legacy или внешний вендор, переговоров нет | Явная фиксация технического долга перевода и homonyms в глоссарии |
| Anti-Corruption Layer (ACL) | Downstream переводит чужую модель в свой UL | Модели несовместимы, но интеграция обязательна | Таблица терминов, правила трансформации, граница «чужой» модели |
| Shared Kernel | Два контекста разделяют общую часть модели | Согласованная команда, общие инварианты критичны | Явный перечень shared-типов, процесс согласования изменений, владелец |
| Separate Ways | Контексты не интегрируются | Дублирование дешевле связи | Обоснование отказа от интеграции |
Anti-Corruption Layer (ACL) — прослойка перевода между моделями контекстов. Внешний «Заказ» из legacy-системы не должен протекать в вашу модель как есть: ACL переводит DTO/API-контракт во внутренние термины UL. При интеграции через API — 03.05.02 Синхронные протоколы (REST, gRPC, GraphQL) и 03.05.03 Спецификации контрактов (OpenAPI, AsyncAPI, Protobuf); через события — 03.05.04 Асинхронное взаимодействие (очереди, Kafka, Events); версионирование на границах — 03.05.07 Управление версионированием API и жизненным циклом. Постепенная замена legacy — 10.02 Разбор кейса "Миграция legacy-системы".
Entity и Value Object
Фундаментальное различие: что имеет идентичность, а что описывается только атрибутами.
Entity (сущность DDD) — объект с уникальной идентичностью, сохраняющейся во времени. Два объекта с одинаковыми атрибутами, но разными ID — разные сущности. Примеры: Клиент, Заказ, Счёт.
Value Object (объект-значение) — без собственной идентичности; определяется атрибутами; обычно иммутабелен; сравнивается по значению. Примеры: Деньги (сумма + валюта), Адрес, Email.
| Критерий | Entity (DDD) | Value Object |
|---|---|---|
| Идентичность | Уникальный ID | Нет |
| Изменяемость | Может менять атрибуты | Иммутабельный (в классическом DDD) |
| Сравнение | По ID | По всем атрибутам |
| Пример | Заказ, Клиент | Адрес, Деньги |
Не путать с ER-сущностью. Entity в DDD — понятие доменной модели с жизненным циклом и поведением; сущность на 03.03.01 ER-диаграммы (сущность-связь) — элемент модели данных (таблица, атрибуты, связи). Один DDD-Entity может мапиться на несколько таблиц или, наоборот, несколько ER-сущностей описывают один агрегат — это решение 03.03.02 Логическая и физическая модель данных, не требований «на языке домена». Структурную нотацию доменных классов см. 06.01.01 Структурные диаграммы (Class, Object, Package).
Aggregate и Aggregate Root
Aggregate — кластер связанных Entity и Value Object, изменяемый как единое целое. Внутри агрегата действуют инварианты — бизнес-правила, которые всегда должны выполняться.
Aggregate Root — главная Entity агрегата; единственная точка входа для изменений извне. Жизненный цикл Root (статусы, переходы) удобно фиксировать на 03.02.04 Диаграмма состояний (State Machine Diagram) — на Ubiquitous Language, с привязкой инвариантов к переходам.
Aggregate: Заказ (Order)
├── Aggregate Root: Order
├── Entity: OrderLine (позиция) — не существует без Order
├── Value Object: Money (сумма)
└── Value Object: Address (адрес доставки)Ключевое правило: одна транзакция = один Aggregate. Если операция затрагивает несколько агрегатов — координация через Domain Events или Saga; см. 03.06.01 Архитектурные паттерны распределённых систем.
Domain Event (доменное событие)
Domain Event — факт, произошедший в домене и значимый для других частей системы: OrderPlaced, PaymentReceived, ClaimAccepted. Это не технический log-entry, а бизнес-событие на Ubiquitous Language.
Что фиксирует аналитик в спецификации:
| Поле | Смысл |
|---|---|
| Имя события | Прошедшее время, UL: OrderShipped, не ship_order |
| Payload | Какие идентификаторы и атрибуты нужны потребителю |
| Источник | Какой Aggregate Root публикует |
| Потребители | Какие контексты/сервисы реагируют |
| Идемпотентность | Ключ для повторной обработки |
| Порядок / версия | Нужна ли строгая последовательность |
События — не только интеграционная шина: downstream может строить read model / проекцию для отображения и отчётов; контракт read-side и задержка обновления — вопросы к команде при проектировании (03.06.01 Архитектурные паттерны распределённых систем, раздел CQRS). Шаблоны producer/consumer — 10.01.06 Шаблон спецификации Producer (Kafka, RabbitMQ) и 10.01.07 Шаблон спецификации Consumer (Kafka, RabbitMQ); топология EDA — 03.05.06 Event-Driven Architecture.
Domain Service и Application Service
Domain Service — операция, не принадлежащая одной Entity: расчёт скидки по политике, matching платежа к счёту. Логика остаётся чисто доменной — без HTTP, БД, очередей.
Application Service — оркестрация use case: загрузить агрегат, вызвать доменную логику, сохранить, опубликовать событие.
Чек для формулировок в UC и sequence-диаграммах:
| Признак в требовании | Слой | Пример формулировки |
|---|---|---|
| Бизнес-правило, инвариант, термины UL | Domain Service / Entity | «Скидка не может превышать маржу по позиции» |
| Шаги «загрузить → проверить → сохранить → опубликовать» | Application Service | «Система загружает Order, вызывает расчёт, сохраняет, публикует OrderPlaced» |
| Вызов внешнего API, транзакция БД, retry | Application / инфраструктура | Не смешивать с доменным правилом в одном пункте AC |
На диаграммах поведения — 03.02.02 Диаграмма деятельности (Activity Diagram) и 03.02.03 Диаграмма последовательности (Sequence Diagram); доменные шаги отделяйте от технической оркестрации.
Repository и Factory
Repository — абстракция доступа к Aggregate Root «как к коллекции в памяти». Доменный слой не зависит от SQL или ORM; интерфейс репозитория принадлежит домену, реализация — инфраструктуре. Один репозиторий = один Aggregate Root; методы на UL: findPendingOrders(), не select * from orders.
Factory — инкапсулирует сложное создание агрегата (валидации, генерация номера, начальные инварианты). Простое создание — конструктор; многошаговое — фабрика.
Для аналитика важна идея, а не код: домен описывается на языке операций над объектами, а не запросов к таблицам. Детали persistence — зона 07.01 Базы данных и SQL на уровне аналитика и команды разработки.
Event Storming (кратко)
Event Storming (Alberto Brandolini) — воркшоп-техника совместного моделирования: команда выписывает доменные события на timeline, группирует их в агрегаты и контексты. Фасилитация — 02.02.02 Воркшопы, фасилитация и совместные сессии и 09.02 Проведение эффективных встреч и воркшопов; до и после сессии термины уточняйте через 02.02.01 Интервьюирование и анкетирование с экспертами предметной области.
Выходы сессии для артефактов аналитика:
| Результат ES | Куда переносится |
|---|---|
| Список доменных событий (UL) | Кандидаты Domain Events, сценарии 03.02.03 Диаграмма последовательности (Sequence Diagram) |
| Группы событий вокруг команд | Кандидаты Aggregate Root и границы транзакций |
| Области разной терминологии | Кандидаты Bounded Context |
| Hot spots (конфликты, неясности) | Пункты для ADR, вопросы в backlog, риски |
| Внешние системы и акторы | Дополнение к 03.01.02 Моделирование контекста системы (Context Diagram) |
| Новые/спорные термины | Черновик 10.01.14 Шаблон глоссария и словаря данных по контекстам |
3. Как применяется в работе аналитика
Глоссарий и требования
- Создайте глоссарий по контекстам: один термин — несколько определений, если контексты разные; помечайте homonyms явно.
- Формулируйте 02.03.01 Классификация требований (БТ, ФТ, НФТ) на Ubiquitous Language; 04.02 Пользовательские истории (User Stories) и Job Stories группируйте по Bounded Context.
- Решения по границам контекстов и типам связей на Context Map фиксируйте в 10.01.13 Шаблон архитектурного решения (ADR).
Инварианты, приёмка и трассировка
Инварианты агрегата переводите в проверяемые правила:
- в 02.04.02 Критерии приёмки (Acceptance Criteria) — «нельзя добавить позицию сверх лимита N»;
- в 10.01.15 Шаблон матрицы трассировки и 02.03.04 Трассировка требований (концепция) — связь правила с Aggregate Root и сценарием;
- в тест-сценариях — граничные случаи нарушения инварианта;
- на 03.02.04 Диаграмма состояний (State Machine Diagram) — допустимые переходы статусов Root.
Интеграция между контекстами
На границе BC доменная модель заканчивается, начинаются контракты. DTO и API-модель — не копия внутренней доменной модели: тип связи и ACL из Context Map определяют, нужен ли перевод (см. §2).
- синхронно — 03.05.02 Синхронные протоколы (REST, gRPC, GraphQL), контракт в 03.05.03 Спецификации контрактов (OpenAPI, AsyncAPI, Protobuf);
- асинхронно — Domain Events по 03.05.04 Асинхронное взаимодействие (очереди, Kafka, Events).
Операции через несколько BC
Когда use case пересекает границы контекстов, в требованиях явно фиксируйте:
| Вопрос | Что указать в спецификации |
|---|---|
| Модель согласованности | Strong внутри BC; между BC — eventual или Saga |
| Ожидаемая задержка | «Статус оплаты виден в заказе в течение N секунд» |
| Сценарии отказа | Частичный успех, компенсация, повторная обработка |
| Длинный процесс | Оркестрация через Saga (03.06.01 Архитектурные паттерны распределённых систем) или сквозной BPMN (03.04.01 BPMN 2.0 - введение и основы, 10.01.08 Шаблон сквозной фичи (BPMN-процесс)) — выбор за ADR |
НФТ на границе BC (идемпотентность, порядок событий, latency, доступность) оформляйте по 08.02 Нефункциональные требования (NFR).
Сводка артефактов
| Артефакт DDD | Что фиксировать в требованиях |
|---|---|
| Bounded Context | Scope, команда-владелец, глоссарий |
| Aggregate | Инварианты, граница транзакции, статусы Root |
| Domain Event | Имя, payload, потребители, идемпотентность |
| Context Map | Тип связи, upstream/downstream, ACL/Shared Kernel |
4. Типовые ошибки и ограничения
- Subdomain = Bounded Context = сервис — смешивает приоритет моделирования, границу модели и deployable unit; три решения принимаются отдельно.
- BC = один микросервис без оговорок — навязывает неверную декомпозицию; проверяйте границы команды и транзакций.
- Единый глоссарий на все BC — homonyms («клиент» в CRM и биллинге) остаются незамеченными; термин без указания контекста вводит в заблуждение.
- Conformist без фиксации долга — downstream молча принимает чужую модель; не закладывают стоимость перевода и риски изменений upstream.
- Путаница Entity (DDD) и ER-сущности — в требованиях смешивают «объект домена» и «таблицу в БД».
- «Божественный» агрегат и нарезка 1:1 по UI или REST-ресурсам — агрегат раздувается или дробится по экранам/API, а не по инвариантам; транзакции и блокировки страдают.
- Прямое изменение внутренних Entity мимо Root — нарушает инварианты; в спецификации все изменения — через Root.
- Domain Event как технический audit log — событие без бизнес-смысла для потребителя; именуйте на UL и указывайте, кто реагирует.
- DDD «везде» — для CRUD-админки или Generic-поддомена полная тактическая модель избыточна; DDD оправдан там, где сложность в домене, а не в UI.
Связи с другими разделами базы
- Архитектура и декомпозиция — 03.01.01 Понятие архитектуры ИС и её виды (бизнес-, системная, техническая), 03.01.02 Моделирование контекста системы (Context Diagram), 03.01.03 Декомпозиция системы на подсистемы и модули, 03.01.04 Интерфейсы и интеграционные взаимодействия
- Интеграция и события — 03.05.01 Виды интеграции (точка-точка, ESB, API-led), 03.05.02 Синхронные протоколы (REST, gRPC, GraphQL), 03.05.03 Спецификации контрактов (OpenAPI, AsyncAPI, Protobuf), 03.05.04 Асинхронное взаимодействие (очереди, Kafka, Events), 03.05.06 Event-Driven Architecture, 03.05.07 Управление версионированием API и жизненным циклом
- Соседняя тема 03.06 — 03.06.01 Архитектурные паттерны распределённых систем
- Моделирование и данные — 03.02.01 Диаграмма прецедентов (Use Case Diagram), 03.02.03 Диаграмма последовательности (Sequence Diagram), 03.02.04 Диаграмма состояний (State Machine Diagram), 03.03.01 ER-диаграммы (сущность-связь), 03.03.03 Словарь данных (описание), 06.03.05 C4-модель
- Требования, scope и NFR — 02.01.04 Определение границ решения (Scope Modelling), 02.03.01 Классификация требований (БТ, ФТ, НФТ), 02.03.04 Трассировка требований (концепция), 02.04.02 Критерии приёмки (Acceptance Criteria), 04.02 Пользовательские истории (User Stories) и Job Stories, 08.02 Нефункциональные требования (NFR)
- Процессы и воркшопы — 03.04.01 BPMN 2.0 - введение и основы, 02.02.01 Интервьюирование и анкетирование, 02.02.02 Воркшопы, фасилитация и совместные сессии
- Артефакты — 10.01.08 Шаблон сквозной фичи (BPMN-процесс), 10.01.13 Шаблон архитектурного решения (ADR), 10.01.14 Шаблон глоссария и словаря данных, 10.01.06 Шаблон спецификации Producer (Kafka, RabbitMQ), 10.02 Разбор кейса "Миграция legacy-системы"
Источники и дополнительные материалы
Книги и стандарты
- Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software (2003)
- Vlad Khononov — Learning Domain-Driven Design (strategic + tactical DDD)
- Alberto Brandolini — Introducing Event Storming (воркшоп-моделирование домена)
- Martin Fowler — Patterns of Enterprise Application Architecture (Repository, Factory — смежные persistence-паттерны, не ядро DDD)
Статьи и онлайн-ресурсы
- Martin Fowler — Domain-Driven Design: https://martinfowler.com/bliki/DomainDrivenDesign.html
- Martin Fowler — Bounded Context: https://martinfowler.com/bliki/BoundedContext.html
- Martin Fowler — Ubiquitous Language: https://martinfowler.com/bliki/UbiquitousLanguage.html
- Martin Fowler — Context Map: https://martinfowler.com/bliki/ContextMap.html
- Microsoft Azure Architecture Center — Use tactical DDD to design microservices: https://learn.microsoft.com/en-us/azure/architecture/microservices/model/tactical-ddd
- Event Storming — https://www.eventstorming.com/