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 нет короткого механического цикла — это методология проектирования, а не практика кодирования. Но есть характерный цикл моделирования:

  1. Исследуй домен — совместно с экспертами, через Event Storming, сессии моделирования, чтение документов.
  2. Построй модель — сформулируй понятия, выдели агрегаты, назови вещи единым языком.
  3. Закодируй модель — воплоти модель в коде тактическими блоками.
  4. Уточни язык — обнаружив, что код расходится с тем, как говорит эксперт, — правь язык или код, пока не совпадут.
  5. Повтори — на каждой итерации модель уточняется по мере углубления понимания домена.

Этот цикл длится не минуты (как 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 как метод декомпозиции.