DDD — Domain Driven Design
Движущая сила: домен (предметная область) и единый язык (ubiquitous language) — модель кода отражает модель бизнеса.
Уровень применения: команда и организация (архитектура + взаимодействие бизнеса и разработки).
Статус: устоявшийся (Eric Evans, 2003; Vaughn Vernon; развит в стратегических и тактических паттернах).
Не путать с: Deadline Driven Development (антипаттерн) и Data Driven — совпадение аббревиатуры.
Общее
Domain Driven Design (DDD) — подход к проектированию программного обеспечения, в котором первичной движущей силой выступает не код, не тесты и не данные, а предметная область (domain): модель бизнеса, выраженная через единый язык и воплощённая в самом коде. Ключевая идея Эванса: когда разработчики и эксперты предметной области пользуются одним и тем же языком для описания одних и тех же понятий, код перестаёт быть «техническим переводом» требований и становится прямым отражением ментальной модели бизнеса. Любое расхождение между тем, как понимает бизнес эксперт, и тем, как это закодировано, трактуется как дефект модели — и подлежит устранению.
Подход был сформулирован Эриком Эвансом (Eric Evans) в книге Domain-Driven Design: Tackling Complexity in the Heart of Software (Addison-Wesley, 2003). Подзаголовок — «борьба со сложностью в сердце ПО» — точно передаёт изначальную мотивацию: DDD создавался не как набор кодовых паттернов, а как метод работы со сложностью предметной области в enterprise-системах. Корни подхода — в объектном моделировании 1990-х (влияние работ Fowler — Analysis Patterns, Patterns of Enterprise Application Architecture; Wirfs-Brock — Responsibility-Driven Design), но именно Эванс собрал разрозненные приёмы в связную методологию с собственным словарём и принципами.
Принципиальное отличие DDD от остальных «*DD» в этой серии — его движущая сила. Если в TDD дизайн направляют тесты, то в DDD дизайн направляет домен. Отсюда вытекает другое: цикл работы выглядит не как «Red-Green-Refactor», а как «исследуй домен → построй модель → закодируй модель → уточни язык → повтори». Тесты в DDD не исчезают, но они перестают быть первичным драйвером — первична модель.
DDD принято делить на два уровня, и это разделение критически важно для понимания:
- Стратегический DDD (strategic design) — отвечает на вопрос «из каких крупных частей состоит система и как они связаны». Оперирует понятиями subdomain (поддомен), bounded context (ограниченный контекст), context map (карта контекстов) и ubiquitous language (единый язык). Стратегический уровень определяет, где проходят архитектурные границы и куда направлять основные усилия.
- Тактический DDD (tactical design) — отвечает на вопрос «как моделировать внутри одного контекста». Оперирует строительными блоками: entity, value object, aggregate, repository, domain service, domain event, factory. Тактический уровень даёт конкретные паттерны кода, но имеет смысл только внутри границ, заданных стратегическим уровнем.
Сводно два уровня можно представить так:
| Ось | Стратегический DDD | Тактический DDD |
|---|---|---|
| Главный вопрос | Из каких частей состоит система и как они связаны | Как моделировать внутри одного контекста |
| Ключевые понятия | Subdomain, Bounded Context, Context Map, Ubiquitous Language | Entity, Value Object, Aggregate, Repository, Domain Service, Domain Event, Factory |
| Уровень решений | Архитектурный (границы, интеграции) | Кодовый (структура классов, инварианты) |
| Кто вовлечён | Архитектор, руководитель, эксперты, команда | Команда и разработчики-моделировщики |
| Что фиксируется | ADR, карта контекстов | Доменный код, тесты модели |
Распространённая ошибка — брать тактические паттерны (агрегаты, репозитории) без стратегической работы (разрезание на контексты, выявление core-домена). Результат: технически «правильный» код, который моделирует не тот домен или моделирует его в неправильных границах. Эванс и Вернон неоднократно подчёркивают: тактика без стратегии бесполезна, тогда как стратегия без тактики уже даёт значительный эффект, потому что границы и язык важнее конкретных классов.
Исторический контекст. После книги Эванса 2003 года подход развивался прежде всего через работы Вона Вернона (Vaughn Vernon) — Implementing Domain-Driven Design (2013), сделавшего стратегический уровень операционно применимым, и Альберто Брандолини (Alberto Brandolini), предложившего Event Storming как практику совместного обнаружения модели. В 2010-х DDD пережил вторую волну популярности благодаря микросервисам: выяснилось, что bounded context — естественная граница сервиса, и стратегический DDD стал де-факто методом проектирования границ микросервисов. К середине 2020-х DDD — устоявшийся термин с устоявшимся словарём, преподаваемый в архитектурных курсах и входящий в базовую грамотность CTO и архитекторов.
Ключевые принципы
DDD — это не «применять паттерны», а набор принципов, которые в совокупности меняют то, как рождается модель. Ниже — формулировка каждого с пояснением, какую конкретную проблему он решает.
Единый язык (ubiquitous language). Разработчики, аналитики и эксперты предметной области пользуются одним и тем же словарём — и этот словарь закреплён в коде. Класс называется так же, как эксперт называет понятие в разговоре; метод называется так же, как бизнес-процесс. Проблема: «телефонный испорченный» перевод между бизнесом и разработкой, когда одно и то же понятие носит три имени (в требованиях, в коде, в голове аналитика). Каждый перевод — точка потери смысла и источник багов. Единый язык устраняет перевод как таковой.
Модель управляет дизайном. Архитектура, структура классов, имена методов — всё подчинено модели предметной области, а не техническим соображениям (таблицам базы, форматам API, удобству фреймворка). Проблема: код, спроектированный «под базу» или «под фреймворк», со временем перестаёт отражать бизнес и становится нечитаемым для экспертов. DDD переворачивает приоритет: модель первична, инфраструктура — подчинена.
Bounded Context как граница модели. Модель не строится «одна на всю систему»: она создаётся в чётко очерченных границах, внутри которых язык и понятия однозначны. Проблема: попытка построить единую модель для крупной системы рождает «мегамодель», в которой каждое понятие перегружено смыслами (что такое «клиент» в продажах, поддержке и биллинге — три разные сущности). Bounded context разделяет эти миры и делает модель внутри каждого осмысленной.
Постоянное взаимодействие с domain experts. Модель не проектируется «в тишине» архитектором — она строится в совместной работе разработчиков и экспертов предметной области, итеративно уточняясь. Проблема: эксперт знает о домене то, чего не знает разработчик; разработчик знает о возможностях кода то, чего не знает эксперт. Без непрерывного диалога модель быстро деградирует в фантазию архитектора. Связь с практиками совместного моделирования — см. Event Storming ниже.
Непрерывное обучение и итеративное моделирование. Модель не «проектируется однажды» — она уточняется на каждой итерации по мере того, как команда глубже понимает домен. Проблема: первая модель всегда наивна (команда не знает домена в глубину в начале проекта); если её зафиксировать как «готовую архитектуру», она перестаёт соответствовать реальности. DDD рассматривает моделирование как процесс, а не как фазу.
Изоляция домена от инфраструктуры. Доменная модель живёт в центре архитектуры и не зависит от технологий доставки (база данных, веб-фреймворк, очереди сообщений). Проблема: доменный код, смешанный с SQL-запросами и HTTP-обработчиками, невозможно читать как модель и невозможно менять без риска сломать инфраструктуру. DDD требует явного разделения: домен — чистый, инфраструктура — снаружи, адаптеры связывают их (схема, известная как hexagonal / ports and adapters / clean architecture).
Модель — не данные. DDD строго разводит модель и схему данных: модель описывает поведение и инварианты предметной области, а таблица базы — лишь одну из возможных проекций этой модели для хранения. Проблема: команды, начинающие проектирование с ER-диаграммы («сначала таблицы, потом классы»), получают data-driven дизайн, в котором доменная логика расползается по сервисам-процедурам, а классы превращаются в контейнеры полей (анемичная модель). DDD переворачивает порядок: сначала модель (поведение), потом — как её сохранить. Хранилище подстраивается под модель, а не наоборот.
Принципы — система, а не меню. Единый язык без bounded context распадается (нельзя иметь «единый» язык, если контекст не ограничен); bounded context без взаимодействия с экспертами — лишь архитектурные прямоугольники без содержания; тактические паттерны без стратегического разрезания — косметика. Частичное внедрение создаёт иллюзию DDD; полное — даёт заявленный эффект: код, читаемый как описание бизнеса.
Как это работает
DDD работает на двух уровнях одновременно. Покажем механику каждого.
Стратегический блок: где проводить границы
Стратегический DDD отвечает на вопрос «как разрезать систему на части так, чтобы каждая часть моделировалась осмысленно». Это уровень принятия архитектурных решений — и именно его результаты фиксируются в ADR.
Subdomain (поддомен). Предметная область бизнеса в целом делится на поддомены — области ответственности, из которых складывается деятельность компании. Эванс и Вернон выделяют три типа поддоменов, и тип определяет инвестиционную стратегию:
- Core domain (ядро). То, ради чего существует бизнес; то, в чём компания выигрывает у конкурентов. Здесь оправданы максимальные инвестиции: лучшие разработчики, самые дорогие эксперты, наибольшая архитектурная зрелость. Core — то, что строят сами (invest).
- Supporting domain (поддерживающий). Не является конкурентным преимуществом, но необходим для работы core. Здесь вкладывают умеренно — достаточно хорошего качества, без избыточной зрелости. Стратегия — партнёрство: делают сами, но без избыточных усилий.
- Generic domain (родовой). То, что одинаково нужно всем в отрасли (аутентификация, отправка email, биллинг) и не является конкурентным преимуществом. Здесь инвестируют минимум — покупают готовое решение (SaaS, open-source, продукт вендора). Стратегия — покупка (buy).
Это разделение — управленческое, не техническое. Оно отвечает на вопрос руководителя: где тратить архитектурные усилия, а где — не тратить. Команда, вкладывающая одинаковые усилия во все поддомены, гарантированно перерасходует на generic и недоработает core.
Покажем на примере. Онлайн-магазин: core-домен — ценообразование и промо-движения (то, за счёт чего магазин выигрывает у конкурентов); supporting — управление каталогом товаров и обработка заказов (нужны, но не дают преимущества); generic — аутентификация, отправка email, платёжный шлюз (стандартные для отрасли). Решения: промо-движения команда строит сама и вкладывает максимум; каталог и заказы — поддерживает на хорошем уровне; аутентификацию и email — покупает как SaaS. Руководитель, направляющий ресурсы по этой карте, не даёт лучшим разработчикам тонуть в «очередной обёртке над SMTP».
Bounded Context (ограниченный контекст). Граница, внутри которой модель и единый язык имеют однозначный смысл. Каждый контекст владеет своей частью модели и не обязан согласовывать понятия с другими. Пример: понятие «клиент» в контексте продаж (человек с историей сделок и сегментом) и в контексте биллинга (юридическое лицо с платёжными реквизитами) — это две разные модели, и DDD запрещает пытаться слить их в одну. Каждый контекст имеет собственную модель, собственную базу (в идеале) и собственный язык.
Context Map (карта контекстов). Описание того, как контексты связаны и взаимодействуют между собой. Связи бывают разного типа: partnership (равноправное партнёрство), customer–supplier (один контекст заказывает, другой поставляет), conformist (один подстраивается под другого без влияния), anti-corruption layer (защитный слой-переводчик, изолирующий контекст от чужой модели), shared kernel (общее ядро, совместно поддерживаемое), open-host service / published language (открытый сервис с опубликованным языком). Карта контекстов — ключевой артефакт архитектуры DDD-проекта; её анализ часто показывает, где нужны антикоррупционные слои, а где — явный контракт между командами.
Покажем карту на примере того же онлайн-магазина. Выделим контексты: «Продажи» (core), «Биллинг», «Доставка», «Каталог», «Идентичность» (generic). Связи между ними:
| Связь | Между контекстами | Смысл |
|---|---|---|
| Customer–Supplier | Продажи (customer) → Биллинг (supplier) | Продажи заказывают выставление счёта; Биллинг поставляет API под нужды продаж |
| Published Language | Продажи → Доставка | Продажи публикуют события (OrderPlaced) на общем языке, который Доставка читает |
| Anti-Corruption Layer | Продажи ↔ Идентичность | Продажи оборачивают чужую модель «пользователя» в свой переводчик, чтобы не поддаться чужому словарю |
| Shared Kernel | Продажи + Биллинг | Совместно поддерживаемый фрагмент общей модели (например, понятие «клиента» в узком смысле) |
Карта делает неявные зависимости явными: видно, какой контекст зависит от какого, кто диктует язык, а кто подстраивается. Это и есть исходный материал для архитектурных решений и фиксации их в ADR.
Ubiquitous Language (единый язык). Словарь терминов, одинаково понимаемый всеми участниками внутри одного bounded context и закреплённый в коде. Единый язык привязан к контексту: термин, однозначный в одном контексте, может означать нечто иное в соседнем — это нормально, потому что контексты разделены. Задача команды — не «создать язык один раз», а поддерживать его: при обнаружении расхождения между разговорным термином и кодом — править код (или язык), пока они снова не совпадут.
Тактический блок: как моделировать внутри контекста
Тактический DDD даёт строительные блоки для кода доменной модели. Каждый блок решает конкретную задачу моделирования. Ниже — таблица соответствий «концепция → роль».
| Концепция | Роль в модели | Ключевое свойство |
|---|---|---|
| Entity (сущность) | Объект с уникальной идентичностью, которая сохраняется во времени | Идентичность (по ID), а не по значениям полей |
| Value Object (объект-значение) | Объект, описываемый только своими значениями, без идентичности | Неизменяемость (immutable); равенство по значениям |
| Aggregate (агрегат) | Группа связанных сущностей и value objects, изменяемых как единое целое | Граница транзакционной согласованности |
| Aggregate Root (корень агрегата) | Единая точка входа в агрегат; единственный объект, на который можно ссылаться снаружи | Внешний мир работает только через корень |
| Repository (репозиторий) | Абстракция хранения и извлечения агрегатов | Интерфейс коллекции в памяти (скрывает БД) |
| Domain Service (доменный сервис) | Операция, относящаяся к домену, но не принадлежащая ни одной сущности | Без состояния; доменная логика, не влезающая в entity |
| Domain Event (доменное событие) | Факт, значимый в домене и случившийся в прошлом | Именуется в прошедшем времени (OrderPlaced); связывает контексты |
| Factory (фабрика) | Инкапсуляция сложной логики создания агрегатов или сущностей | Скрывает детали конструирования; восстановление инвариантов |
Несколько нюансов, критичных для понимания:
Aggregate — не «сущность побольше». Агрегат вводится ради одного: границы согласованности. Внутри агрегата данные всегда согласованы (выполняются инварианты); между агрегатами — согласованность eventual, через события. Отсюда следствие: агрегаты нужно делать маленькими. Инстинкт «объединить всё в один большой агрегат» — частая ошибка, убивающая производительность и порождающая блокировки. Это практический принцип, закрепившийся в DDD-сообществе (Vernon, IDDD): агрегат должен быть настолько мал, насколько это возможно, чтобы защитить свои инварианты.
Value Object недооценивают. Начинающие команды моделируют почти всё как entity (с идентичностью). Между тем value object — самый недоиспользуемый и одновременно самый полезный блок: деньги (Money), адреса, периоды дат, координаты — всё, что описывается значениями, должно быть value object. Их неизменяемость упрощает рассуждения о коде и устраняет целый класс мутационных багов.
Aggregate Root как единая дверь. Внешний код не может напрямую обращаться к внутренним сущностям агрегата — только через корень. Это гарантирует, что инварианты проверяются всегда: нет «задней двери», через которую агрегат можно сломать, минуя логику корня.
Repository скрывает базу. Доменный код работает с репозиторием как с коллекцией в памяти (orders.findBy(customerId), orders.add(order)). Детали хранения (SQL, NoSQL, файл) — в реализации, скрытой за интерфейсом. Это и есть изоляция домена от инфраструктуры.
Domain Event — клей между контекстами. Событие OrderPlaced в контексте продаж — факт, на который может реагировать контекст биллинга (выставить счёт) и контекст доставки (запланировать отгрузку). События — один из ключевых механизмов интеграции bounded contexts: они особенно хороши там, где нужна eventual consistency и слабая связанность между контекстами; для синхронных сценариев команды нередко используют прямые API-вызовы. События также естественная основа для event-driven и микросервисных архитектур.
Пример: моделирование заказа
Покажем, как тактические блоки складываются в модель. Домен: оформление заказа. Не претендуя на полноту, проиллюстрируем решения.
Заказ — это aggregate: он согласован внутри себя (общая сумма, статус, состав позиций), и инвариант — «сумма заказа равна сумме его позиций» — должен выполняться всегда. Корень агрегата — Order: через него добавляются позиции, меняется статус, проверяются правила. Позиция заказа (OrderLine) — entity внутри агрегата (у неё своя идентичность внутри заказа), но снаружи агрегата на неё нельзя ссылаться напрямую. Деньги (Money) и адрес доставки (Address) — value objects: они описываются значениями и не имеют идентичности, а их неизменяемость исключает случайные мутации.
class Money: # value object — immutable, равенство по значениям
def __init__(self, amount, currency):
self._amount = amount
self._currency = currency
def add(self, other):
if other._currency != self._currency:
raise ValueError("Currency mismatch")
return Money(self._amount + other._amount, self._currency)
def multiply(self, factor):
return Money(self._amount * factor, self._currency)
class Order: # aggregate root — единая дверь, защищает инварианты
def __init__(self, order_id, customer_id):
self._id = order_id
self._customer_id = customer_id
self._lines = [] # OrderLine entities, доступны только через корень
self._status = "draft"
def add_line(self, product, quantity, unit_price): # инвариант проверяется в корне
line = OrderLine(product, quantity, unit_price)
self._lines.append(line)
def total(self):
total = Money(0, "RUB")
for line in self._lines:
line_total = line.unit_price().multiply(line.quantity())
total = total.add(line_total)
return total
def place(self):
if not self._lines:
raise DomainError("Нельзя разместить пустой заказ") # инвариант
self._status = "placed"
return OrderPlaced(self._id, self._customer_id, self.total()) # domain event
На этом примере видны ключевые решения: инвариант («нельзя разместить пустой заказ») живёт внутри агрегата, а не в отдельном сервисе; внешние операции идут через корень; факт размещения заказа порождает событие OrderPlaced, на которое реагируют другие контексты. Это и есть «код как отражение модели»: читая Order.place(), эксперт видит знакомое бизнес-действие без перевода.
Цикл работы
В отличие от TDD с его Red-Green-Refactor, у DDD нет короткого механического цикла — это методология проектирования, а не практика кодирования. Но есть характерный цикл моделирования:
- Исследуй домен — совместно с экспертами, через Event Storming, сессии моделирования, чтение документов.
- Построй модель — сформулируй понятия, выдели агрегаты, назови вещи единым языком.
- Закодируй модель — воплоти модель в коде тактическими блоками.
- Уточни язык — обнаружив, что код расходится с тем, как говорит эксперт, — правь язык или код, пока не совпадут.
- Повтори — на каждой итерации модель уточняется по мере углубления понимания домена.
Этот цикл длится не минуты (как TDD), а итерации проекта; но его дисциплина — «всегда держать код отражением модели» — и есть суть подхода.
Покажем на микро-примере, как уточняется язык. На первой сессии команда и эксперт договорились называть статус заказа PAID («оплачен»). Через неделю в разговоре эксперт дважды называет «оплаченным» заказ, по которому деньги поступили на счёт, и заказ, по которому только списание подтверждено банком. Это расхождение языка и кода. Команда останавливается и вводит два термина: AUTHORIZED (средства захолдированы) и CAPTURED (средства получены). Код правится под уточнённый язык, глоссарий обновляется. Модель стала точнее — и именно ради таких моментов цикл существует. Каждое уточнение языка — устранение скрытого источника будущих дефектов.
Влияние на команду и процесс
DDD часто обсуждают как набор архитектурных паттернов. Это поверхностное чтение: устойчивый эффект возникает только тогда, когда DDD встроен в командный процесс и организационную структуру. Ниже — что именно меняется. Этот блок адресован прежде всего руководителям.
Топология команд вокруг bounded contexts. Если bounded context задаёт границу модели, то естественно — выстроить границу команды вокруг контекста. Одна команда владеет одним (реже — несколькими) контекстом целиком: моделью, кодом, языком, базой. Это прямое проявление закона Конвея: архитектура системы повторяет структуру коммуникаций организации, и DDD даёт осмысленный способ спроектировать организацию под желаемую архитектуру (reverse Conway manoeuvre), а не мириться с тем, что структура отделов диктует хаотичные границы в коде. Это связывает стратегический DDD напрямую с декомпозицией проекта.
Роли в команде. DDD переопределяет распределение ответственности:
- Domain expert (эксперт предметной области) — носитель знаний о бизнесе; активный участник моделирования, а не «заказчик, которого опрашивают в начале». Без постоянного доступа к эксперту DDD не работает (см. «Когда НЕ использовать»).
- Аналитик / domain analyst — связующее звено: помогает переводить неявные знания эксперта в явные термины модели; часто — фасилитатор сессий моделирования.
- Разработчик — не только кодер, но и моделировщик: его задача — совместно с экспертом находить точные имена и границы, а затем воплощать их в коде. Разработчик, не участвующий в моделировании, — признак вырождения DDD в «применение паттернов».
Event Storming как практика обнаружения модели. Метод, предложенный Альберто Брандолини: совместная сессия (эксперты + разработчики + аналитики), на которой участники клеят на большую доску стикеры доменных событий (OrderPlaced, PaymentReceived, ShipmentDispatched) и выстраивают их в поток. Цель — быстро (за один-два дня) обнаружить границы контекстов, инварианты и пробелы в понимании. Event Storming — типичный запуск DDD-инициативы: он заменяет многомесячное «чтение ТЗ» интенсивным совместным исследованием и часто даёт первые наброски bounded contexts уже к концу первой сессии.
Влияние на планирование и оценку. Фаза моделирования — «дорогая»: она требует времени экспертов, нескольких итераций уточнения, сессий совместной работы. Руководитель, ожидающий быстрой отдачи, воспримет это как задержку. Это управленческая ошибка: инвестиция в точную модель окупается на горизонте месяцев и лет тем, что код перестаёт сопротивляться изменениям бизнеса. Оценивать DDD по скорости первых спринтов — типичная и разрушительная ошибка, аналогичная оценке TDD по скорости первого цикла.
Организационные шаблоны. Вернон в Implementing Domain-Driven Design систематизировал организационные формы команд, поддерживающие DDD:
- Big Ball of Mud (BB) — хаотичная архитектура без явных границ; антипаттерн, но встречается повсеместно как стартовая точка легаси.
- Contextualized Service (CS) — сервис, чья граница совпадает с одним bounded context; целевая форма для микросервисов.
- Platform (PL) — команда, поставляющая generic-поддомены как внутреннюю платформу для остальных команд (стратегия «покупки/построения» generic).
- Product Development (PD) — команда, владеющая core-доменом и непрерывно развивающая его как продукт.
Эти шаблоны — не академическая классификация, а инструмент: руководитель, понимающий, где core, где generic и какие команды какими контекстами владеют, получает осмысленный способ направлять инвестиции (в core — лучшие силы, в generic — платформа, в supporting — партнёрские команды).
| Шаблон команды | Что владеет | Инвестиционная стратегия | Типичный пример |
|---|---|---|---|
| Product Development (PD) | Core-домен | Максимальные инвестиции, лучшие силы | Команда «промо-движений» в ритейле |
| Contextualized Service (CS) | Один bounded context | Поддержание качества, без избыточности | Команда «заказов» в e-commerce |
| Platform (PL) | Generic-поддомены | Покупка/построение как внутренний продукт | Команда «аутентификации и уведомлений» |
| Big Ball of Mud (BB) | Хаос без границ | Технический долг; цель — выйти из этого состояния | Типовой legacy-монолит |
Связь с микросервисами. Bounded context часто служит границей микросервиса — практическое правило, популяризированное в 2010-х. Но это не тождество: один bounded context может быть реализован несколькими сервисами (например, вынесенными read-моделями), а несколько тесно связанных контекстов могут жить в одном сервисе. Главное — bounded context задаёт логическую границу модели, а микросервис — физическую границу развёртывания; первое первично, второе — производно. Решение о границах сервисов всегда принимается с учётом дополнительных критериев (командные границы, нагрузка, требования к доступности), и слепое «один контекст = один сервис» — распространённый источник «распределённого монолита», где сервисы связаны сильнее, чем модули внутри одного приложения.
Онбординг и передача знаний. Код, в котором домен выражен единым языком, читается как описание бизнеса — это радикально снижает порог входа для новых разработчиков. Вместо расшифровки технических имён и восстановления контекста по комментариям новичок читает классы, названные терминами экспертов, и быстро погружается в предметную область. Глоссарий ubiquitous language превращается в живую документацию, которая не устаревает (потому что живёт в коде), а bounded context ограничивает область, которую нужно понять для начала работы. Это косвенно повышает bus-factor проекта и снижает зависимость от отдельных «хранителей знания».
Где DDD живёт в SDLC. В жизненном цикле разработки DDD прежде всего действует на фазах анализа и проектирования: построение модели — это и есть проектирование. Но в отличие от классического «водопадного» проектирования (один раз в начале), DDD-моделирование распределено по всему циклу — модель уточняется на каждой итерации. Поэтому DDD естественно сочетается с итеративными и Agile-процессами: каждый спринт даёт и уточнённую модель, и код, ей соответствующий.
Преимущества
Общее понимание. Единый язык устраняет «перевод» между бизнесом и разработкой. Требование, модель и код говорят на одном языке; meeting и кодовая база не расходятся. Это снижает недопонимание — главную скрытую причину дефектов в сложных доменах.
Изоляция сложности. Bounded contexts разделяют большую сложную систему на части, каждая из которых моделируется осмысленно в своих границах. Команда, работающая над одним контекстом, не обязана держать в голове всю систему — она работает с управляемой частью модели.
Эволюционность модели. Итеративное моделирование и явные границы делают модель изменяемой: новые требования уточняют модель внутри контекста, не вызывая цепной реакции по всей системе. Это прямой вклад в эволюционность архитектуры — модель становится способной развиваться вместе с бизнесом.
Переиспользование через контексты, а не через код. DDD не стремится к «переиспользованию кода» (часто иллюзорному). Вместо этого переиспользуются модели и языки: generic-поддомены реализуются платформой и предоставляются командам через контракты; core-модели переиспользуются внутри своего контекста.
Читаемость кода как отражение бизнеса. Код DDD-проекта читается как описание предметной области: классы названы терминами экспертов, методы соответствуют бизнес-операциям, события — реальным фактам. Это радикально снижает порог входа для новых разработчиков и облегчает онбординг: понять «что делает система» можно, читая доменный код, а не расшифровывая технические имена.
Управляемые инвестиции. Разделение subdomain на core/supporting/generic даёт руководителю явный ответ: «куда вкладывать архитектурные усилия». Это дисциплинирует ресурсное планирование и предотвращает расточительство на том, что можно купить готовым.
Тестируемость модели. Чистый доменный слой с явными инвариантами, инкапсулированными в агрегатах, тестируется легко и предсказуемо: репозиторий подменяется, внешние эффекты изолируются, и поведение модели проверяется как чистая логика. Это естественная точка интеграции с TDD: модель, спроектированная по DDD, удобна для цикла Red-Green-Refactor, потому что её инварианты выражены явно и проверяются без инфраструктуры.
Недостатки и риски
Оверинжиниринг (over-modelling) для простых доменов. DDD — тяжёлая методология: стратегический анализ, сессии моделирования, тактические паттерны. Применение всей этой машины к CRUD-домену или простому MVP даёт чистые убытки: затраты на моделирование не окупаются простой логикой. DDD оправдан только там, где сложность домена оправдывает инвестицию; в простых доменах нужны более лёгкие подходы (подробнее — «Когда НЕ использовать»).
Накладные расходы на ubiquitous language. Поддержание единого языка — это явная работа: фиксация глоссария, постоянная сверка разговорных терминов с кодом, правки при обнаружении расхождений. Команда, формально объявившая «единый язык», но не поддерживающая его, быстро возвращается к привычному «каждый называет по-своему». Это вопрос дисциплины и культуры, а не инструментов.
Зависимость от доступа к domain experts. DDD не работает без постоянного взаимодействия с экспертами предметной области. Если эксперты недоступны (заняты, уволились, находятся в другом часовом поясе и не выделены на проект), моделирование деградирует в фантазию архитектора, которая не соответствует реальному бизнесу. Это жёсткое ограничение применимости — см. «Когда НЕ использовать».
Риск «anemic domain model» (анемичной доменной модели). Распространённый антипаттерн: классы домена содержат только данные (поля и геттеры/сеттеры), а вся логика вынесена в сервисы. Внешне это похоже на DDD (есть entities, есть repositories), но по сути — процедурное программирование в объектной обёртке. Модель при этом перестаёт быть моделью: она не инкапсулирует поведение, инварианты проверяются где-то снаружи, и вся выгода DDD теряется. Мартин Фаулер ввёл этот термин именно как критику типичного злоупотребления.
Долгий ROI. Инвестиции в модель окупаются на горизонте месяцев и лет, а не спринтов. В первые итерации DDD-проект выглядит медленнее, чем «просто написать код»: время уходит на сессии моделирования, уточнение языка, проектирование контекстов. Руководитель, оценивающий по скорости первых спринтов, увидит задержку и может отказаться от подхода преждевременно — управленческая ошибка, аналогичная ранней критике TDD.
Сложность внедрения в legacy. Применить DDD к существующей кодовой базе без чётких границ («big ball of mud») — тяжёлая задача: приходится вводить контексты ретроспективно, выделять антикоррупционные слои, постепенно отсекать технический долг. Это возможно (подход strangling — постепенное вырезание), но требует долгосрочного обязательства, а не разового рефакторинга.
Навык и дисциплина. DDD — не «прочитал книгу и применил». Эффективное моделирование требует развитого навыка: умения слушать эксперта, находить точные имена, чувствовать границы агрегатов, различать entity и value object в конкретной ситуации. В команде без этого навыка DDD вырождается в формальное применение паттернов без эффекта («DDD theater»).
Производительность и размер агрегатов. Большие агрегаты, которые блокируют всю связанную графу при каждой транзакции, — частый источник проблем с производительностью и конкуренцией за блокировки в базе. Решение — то же правило «маленьких агрегатов», но его соблюдение требует опыта: нужно уметь делить модель так, чтобы инварианты по-прежнему защищались, а транзакции не захватывали лишнее. В высоконагруженных системах этот баланс между согласованностью и производительностью — отдельное инженерное искусство.
Сложность интеграции контекстов. Стратегический DDD облегчает проектирование границ, но не снимает сложности самой интеграции: синхронизация через API, eventual consistency через события, антикоррупционные слои — всё это реальные затраты на разработку и сопровождение. Команда, разбившая систему на контексты, но не заложившаяся на стоимость их связи, получает распределённую систему с неожиданными накладными расходами.
Когда использовать
- Сложный core-domain. Предметная область с богатой логикой, множеством инвариантов, неочевидными правилами — главный случай для DDD. Здесь инвестиция в модель окупается многократно.
- Долгоживущие системы. Если система будет развиваться годами, точная модель — страховка от деградации кода в хаос; для одноразовых скриптов DDD избыточен.
- Доступные domain experts. Если эксперты предметной области готовы выделять время на постоянное взаимодействие (сессии моделирования, ответы на вопросы) — DDD работает. Это предусловие, а не пожелание.
- Микросервисная архитектура. Когда нужно спроектировать границы сервисов — стратегический DDD даёт осмысленный метод (bounded context как граница сервиса). Это современный главный драйвер применения DDD.
- Неоднозначная лексика бизнеса. Если одно и то же понятие («клиент», «заказ», «аккаунт») значит разное в разных частях бизнеса — это сигнал: нужен bounded context, чтобы перестать пытаться слить несовместимые смыслы в одну модель.
- Enterprise и сложные предметные области. Финтех, страхование, логистика, здравоохранение — классические домены DDD, где сложность бизнеса оправдывает методологию.
Когда НЕ использовать
- CRUD и простые домены. Если приложение в основном читает и записывает данные без сложной логики — DDD избыточен. Здесь работают простые архитектуры (таблица-форма, REST поверх БД), а тактические паттерны DDD только добавят бесполезной сложности.
- MVP и прототипы. Когда цель — быстро проверить гипотезу, инвестиции в модель преждевременны: требования ещё меняются слишком быстро, чтобы фиксировать модель. Сначала — гипотеза, потом (если выживет) — DDD.
- Нет доступа к domain experts. Это жёсткое противопоказание. Без постоянного взаимодействия с экспертами модель неизбежно деградирует в фантазию. Лучше выбрать подход, не требующий глубокой доменной работы, чем применять DDD формально.
- Короткий горизонт. Если система живёт месяцы, а не годы, инвестиция в модель не успеет окупиться. DDD — стратегический выбор для долгоживущих систем.
- Команда без навыка моделирования и без времени на обучение. Внедрение DDD «с понедельника» в команде без инженерной зрелости даёт формальные агрегаты и репозитории без эффекта. Сначала — обучение и пилот; потом — масштабирование.
- Технически-ориентированные системы. Инфраструктурные сервисы, утилиты, middleware — там, где домен не «бизнес», а «технология», DDD избыточен: модель и так ясна (протоколы, буферы, очереди), и единый язык с экспертом-нетехнарем не нужен.
Связанные подходы
DDD — не единственный «*DD» в разработке; за разными аббревиатурами стоят разные «движущие силы». Статья входит в серию материалов о driven-подходах; ниже — краткая карта, со ссылками на уже опубликованные материалы базы знаний.
| Подход | Движущая сила | Что управляет дизайном | Уровень применения |
|---|---|---|---|
| DDD | Домен | Модель предметной области и единый язык | Команда и организация |
| TDD | Тесты | Тесты как исполняемая спецификация (Red-Green-Refactor) | Код и команда |
| BDD | Поведение | Сценарии Given-When-Then на уровне требований | Команда и заказчик |
| FDD | Функция (фича) | Процесс доставки фичи: модель → список фич → реализация | Команда и организация |
| ATDD (в работе серии) | Критерии приёмки | Тесты приёмки как драйвер | Вся команда с заказчиком |
- TDD — Test Driven Development — родственный подход, где движущей силой выступают тесты как исполняемая спецификация. DDD и TDD дополняют друг друга: DDD задаёт модель и язык, TDD страхует поведение модели тестами. Принципиальная разница — в том, что именно управляет дизайном (домен vs тесты).
- ADR — фиксация архитектурных решений, в том числе о разрезании на bounded contexts и выборе стратегий интеграции контекстов (partnership, anti-corruption layer и др.).
- Эволюционная архитектура — итеративное моделирование DDD и явные границы контекстов — вклад в способность архитектуры развиваться вместе с бизнесом.
- Паттерны проектирования — тактические блоки DDD (entity, value object, aggregate, repository) опираются на классические паттерны проектирования и уточняют их под доменное моделирование.
- HLD и LLD — стратегический DDD (context map, subdomains) отражается на уровне HLD; тактический (агрегаты, доменные сервисы) — на уровне LLD.
- Закон Конвея — bounded context как граница команды; проектирование организации под желаемую архитектуру (reverse Conway manoeuvre).
- Декомпозиция проекта — стратегический DDD как метод содержательной декомпозиции сложной системы на контексты.
- SDLC — где DDD живёт внутри жизненного цикла (прежде всего — фазы анализа и проектирования, на которых строится модель).
- AIDD — AI-Driven Development: DDD-модель и единый язык служат контекстом для AI-генерации доменного кода, а bounded contexts задают границы, в которых спецификации AI остаются осмысленными.
- BDD — Behaviour Driven Development — родственный подход, где движущей силой выступает поведение в сценариях Given-When-Then. DDD и BDD разделяют один принцип — ubiquitous language, — но применяют его к разным артефактам: DDD к модели предметной области, BDD к требованиям и примерам поведения.
- FDD — Feature Driven Development — организационная методология, где движущей силой выступает клиенто-ориентированная фича. FDD разделяет с DDD опору на модель предметной области, но использует её операционно — для декомпозиции на фичи, — тогда как DDD — как непрерывно эволюционирующий язык и архитектурный каркас.
- MDD — Model Driven Development — родственный модельно-ориентированный подход. Общее: опора на модель как первичный артефакт. Различие: DDD говорит о модели предметной области (домен как язык и источник паттернов), MDD — о формальной модели системы (UML/DSL как источник кода). DDD-модель может быть входом для MDD-трансформаций.
- RDD — Risk Driven Development — риск-ориентированное проектирование: сколько архитектуры достаточно, решает реестр рисков. RDD и DDD дополняют друг друга: риск «неверная доменная модель» закрывается техниками стратегического DDD, а сам DDD в терминах RDD — набор архитектурных техник для рисков сложности домена.
Родственные driven-подходы — BDD — Behaviour Driven Development (поведение и общий язык на уровне требований), FDD — Feature Driven Development (фича как единица доставки), MDD — Model Driven Development (формальная модель как источник истины), ATDD (критерии приёмки), а также «сатирические» варианты вроде Panic Driven Development — рассматриваются в отдельных статьях серии.
Краткий вердикт для руководителя
DDD — это инвестиция в понимание и структуру сложной предметной области, а не способ писать код быстрее здесь и сейчас. Эффект: общее понимание бизнеса и кода, изоляция сложности по контекстам, читаемость кода как отражение бизнеса, осмысленные границы микросервисов. Цена: тяжёлая фаза моделирования, зависимость от постоянного доступа к экспертам, необходимость обучать команду и поддерживать единый язык как явную работу. Берите, если домен сложен, система долгоживущая, эксперты доступны, а команда готова к инженерной дисциплине моделирования. Не берите, если домен прост (CRUD), горизонт короткий, экспертов не получить, или процессом управляет паника дедлайнов. Оценивать эффект DDD на горизонте одного спринта — главная ошибка внедрения; эффект проявляется на горизонте месяцев и лет, когда модель начинает «работать на вас», а не сопротивляться каждому изменению.
Источники и материалы
Первоисточники и ключевые публикации
- Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley, 2003 — фундаментальный первоисточник, формулировка стратегических и тактических паттернов, принципов ubiquitous language и bounded context.
- Eric Evans. Domain-Driven Design Reference: Definitions and Pattern Summaries. 2014 — сводный справочник паттернов DDD (краткий «карманный» референс).
- Vaughn Vernon. Implementing Domain-Driven Design. Addison-Wesley, 2013 — операционное воплощение DDD: шаблоны агрегатов, контекстные карты, организационные формы команд.
- Alberto Brandolini. Introducing Event Storming. — практика совместного обнаружения модели и доменных событий.
- Martin Fowler. BoundedContext, UbiquitousLanguage, AnemicDomainModel (martinfowler.com) — аналитические статьи, разъясняющие ключевые понятия и антипаттерны.
- Eric Evans. Domain-Driven Design and Me (доклады и статьи 2010-х) — рефлексия автора о развитии подхода через десятилетие после публикации книги.
Связанные материалы базы знаний
- TDD — Test Driven Development — родственный driven-подход; тесты как движущая сила против домена как движущей силы.
- BDD — Behaviour Driven Development — общий источник принципа ubiquitous language; терминология BDD-сценариев берётся из модели предметной области.
- ADR — фиксация архитектурных решений о границах контекстов и стратегиях интеграции.
- Эволюционная архитектура — DDD как вклад в способность архитектуры развиваться.
- Закон Конвея — bounded context как граница команды.
- Декомпозиция проекта — стратегический DDD как метод декомпозиции.