← назад к разделу

Со временем большинство серьёзных проектов превращается в трудноизменяемые системы. DDD — это подход, который помогает этого избежать, привязывая структуру кода к бизнес-домену. Разберём с нуля: в чём проблема, как её решает DDD и когда это действительно нужно.

Проблема: бизнес и код говорят на разных языках

Представь типичный корпоративный проект через пару лет разработки:

  • Бизнес говорит «заявка», в коде — полный разброд. Request, Order, Ticket и Application в разных сервисах — и все про одно и то же.
  • Изменение бизнес-правила ломает всё. «Добавить скидку для VIP» превращается в правку 15 файлов, потому что логика размазана по обработчикам, сервисам и SQL-запросам.
  • Модули тянут друг друга. Платёжный модуль знает про склад, склад — про уведомления. Любое изменение вызывает каскад.
  • Новый человек тонет. Погружение занимает месяцы, потому что нет явной структуры и единого языка.

Domain-Driven Design (DDD) — это подход, в котором структура и язык кода привязаны к бизнес-домену: к тому, как бизнес описывает свои процессы. Не к фреймворку, не к базе данных, не к API. Термин ввёл Эрик Эванс в книге «Domain-Driven Design: Tackling Complexity in the Heart of Software» (2003).

Ubiquitous Language — единый язык команды

Первое, с чего начинается DDD: разработчики и бизнес-эксперты договариваются об общем словаре. Этот словарь называется Ubiquitous Language (единый язык).

Что это значит на практике:

  • термины из кода совпадают с терминами из разговоров с бизнесом;
  • если бизнес говорит «заявка», класс называется Application, а не Request или Ticket;
  • если появляется новый бизнес-термин — он сразу появляется в коде.

Без единого языка возникает «перевод» между тем, что хочет бизнес, и тем, что написано в коде. Перевод — это потери: неточности, баги, потраченное время на выяснения.

Bounded Context — граница, где слова однозначны

Одно и то же слово в разных частях системы может означать разное. «Клиент» в отделе продаж — это потенциальный покупатель. «Клиент» в бухгалтерии — это плательщик с реквизитами. «Заказ» в момент оформления и «заказ» на складе — совершенно разные наборы данных.

Bounded Context (ограниченный контекст) — это явная граница, внутри которой термины имеют однозначный смысл. Каждый контекст живёт своей моделью, и это нормально: не нужно мучить одну универсальную модель «Заказа», пытаясь угодить всем.

Один контекст — одна команда, один набор терминов, один язык. На пересечениях контекстов — явные правила взаимодействия.

Aggregate — граница изменений

Внутри одного контекста данные не существуют в изоляции: объекты связаны между собой. Но если менять их как попало — нарушается целостность.

Aggregate — это кластер связанных объектов с одним корневым объектом (Aggregate Root). Все изменения идут только через корень. Это обеспечивает целостность: нельзя поменять строку заказа, не пройдя через сам заказ.

Пример: Order — корень агрегата, OrderLine — внутренний объект. Нельзя добавить строку, минуя метод Order.addLine(...). Агрегат сам следит за своими правилами.

Граница агрегата — это также граница транзакции: одна операция меняет один агрегат.

Domain Event — что произошло в домене

Когда что-то важное происходит в системе, об этом нужно сообщить другим частям. Domain Event — это факт, который уже случился в домене.

Примеры: OrderConfirmed («заказ подтверждён»), PaymentReceived («платёж принят»), ItemShipped («товар отправлен»).

Ключевое слово — «произошло». Событие описывает прошлое, а не команду на будущее. Другие контексты могут реагировать на событие, но не диктуют, как оно обрабатывается внутри источника.

События делают контексты слабосвязанными: платёжный сервис не знает про доставку, он просто публикует PaymentReceived, а доставка подписывается и реагирует.

Стратегические и тактические паттерны

DDD работает на двух уровнях:

Стратегический уровень — как разбить систему на части:

  • определить Bounded Context'ы и их границы;
  • нарисовать Context Map — кто с кем общается;
  • выделить Core Domain (ядро бизнеса, где максимальная сложность) и вспомогательные поддомены.

Тактический уровень — как писать код внутри одного контекста:

  • Aggregate и Aggregate Root;
  • Domain Event;
  • Value Object (объект-значение без идентичности: деньги, адрес, координаты);
  • Repository (абстракция над хранилищем агрегата);
  • Domain Service (логика, которая не принадлежит ни одному агрегату).

Частая ошибка — начинать сразу с тактических паттернов, не разобравшись со стратегическими. Сначала нужно понять границы и язык, потом — писать код.

Когда DDD оправдан, а когда нет

DDD — это инструмент для сложности. Там, где сложности нет, он добавит её сам.

DDD оправдан, когда:

  • бизнес-логика нетривиальна и часто меняется;
  • проект долгоживущий — годы, не месяцы;
  • несколько команд работают над одной системой;
  • домен сложный: маркетплейс, финансы, логистика, страхование;
  • разработчики и бизнес-эксперты могут регулярно общаться.

DDD избыточен, когда:

  • приложение — простой CRUD без существенной бизнес-логики;
  • маленькая команда, один контекст, понятные требования;
  • прототип, который нужно запустить быстро;
  • нет доступа к бизнес-экспертам.

Хорошее правило: если вся бизнес-логика помещается в один if в обработчике запроса — DDD не нужен. Если бизнес-правила занимают сотни строк и продолжают расти — стоит задуматься.

Как начать

Если решили попробовать:

  1. Поговорите с бизнесом. Event Storming — хороший формат для начала: собираете команду и выписываете все события в домене.
  2. Зафиксируйте глоссарий. Список терминов — это ваш Ubiquitous Language. Пусть он живёт в документе и дополняется.
  3. Определите контексты. Нарисуйте Context Map — кто с кем общается, кто от кого зависит.
  4. Начните с одного контекста. Выберите Core Domain и примените тактические паттерны. Не пытайтесь переписать всё сразу.

Коротко

  • DDD привязывает структуру кода к бизнес-домену, а не к фреймворку или базе данных.
  • Ubiquitous Language — единый словарь разработчиков и бизнеса; термины в коде совпадают с бизнес-терминами.
  • Bounded Context — явная граница, внутри которой термины однозначны; у каждого контекста своя модель.
  • Aggregate — кластер объектов с корнем; все изменения через корень, одна транзакция — один агрегат.
  • Domain Event — факт, который произошёл; делает контексты слабосвязанными.
  • Стратегический уровень (границы, Context Map) первичен; тактические паттерны — вторичны.
  • DDD оправдан при сложной и меняющейся бизнес-логике; для простого CRUD он избыточен.

Что почитать дальше

  • Стратегические паттерны DDD — Bounded Context, Context Map, типы поддоменов.
  • Тактические паттерны DDD — Aggregate, Entity, Value Object, Repository, Domain Service.
  • Интеграционные паттерны DDD — как контексты общаются между собой.