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-SupplierUpstream поставляет модель downstream по договорённостиЕсть переговорная сила downstreamSLA на изменения контракта, приоритет запросов downstream
ConformistDownstream принимает модель 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-диаграммах:

Признак в требованииСлойПример формулировки
Бизнес-правило, инвариант, термины ULDomain Service / Entity«Скидка не может превышать маржу по позиции»
Шаги «загрузить → проверить → сохранить → опубликовать»Application Service«Система загружает Order, вызывает расчёт, сохраняет, публикует OrderPlaced»
Вызов внешнего API, транзакция БД, retryApplication / инфраструктураНе смешивать с доменным правилом в одном пункте 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. Как применяется в работе аналитика

Глоссарий и требования

Инварианты, приёмка и трассировка

Инварианты агрегата переводите в проверяемые правила:

Интеграция между контекстами

На границе BC доменная модель заканчивается, начинаются контракты. DTO и API-модель — не копия внутренней доменной модели: тип связи и ACL из Context Map определяют, нужен ли перевод (см. §2).

Операции через несколько 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 ContextScope, команда-владелец, глоссарий
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.

Связи с другими разделами базы

Источники и дополнительные материалы

Книги и стандарты

  • 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)

Статьи и онлайн-ресурсы