Введение в предметно-ориентированное проектирование

Tommy Cheese | · Время чтения: 7 мин

Обзор предметно-ориентированного проектирования

Предметно-ориентированное проектирование (DDD, Domain-Driven Design) — подход к созданию программного обеспечения вокруг предметной области бизнеса. Разработчики и эксперты совместно изучают бизнес, формируют доменную модель и отражают её понятия, правила и язык в коде. Это помогает понимать и сопровождать сложную бизнес-логику.

DDD включает два взаимосвязанных уровня: стратегическое и тактическое проектирование. Стратегическое рассматривает поддомены, ограниченные контексты и их взаимодействие. Тактическое описывает сущности, объекты-значения, агрегаты, доменные сервисы и события внутри контекста. Оба уровня постоянно уточняются на практике.

Пример интернет-магазина: бизнес-поддомены сопоставлены с ограниченными контекстами; контекст заказов содержит агрегат заказа, корень которого является сущностью.
Это один из возможных вариантов. Соответствие поддоменов и контекстов определяется при моделировании; агрегаты находятся внутри конкретного контекста.

Понятия стратегического проектирования

Предметная область

Предметная область — область бизнес-знаний и деятельности, которой занимается программа. Интернет-магазин может охватывать продажи товаров, расчёты по платежам и исполнение заказов. Для понимания области нужно обсуждать цели, процессы и правила бизнеса, а не только перечислять функции системы.

Поддомены

Поддомены выделяют по бизнес-возможностям, а затем классифицируют по вкладу в конкурентное преимущество и наличию зрелых универсальных решений:

  • Ключевые поддомены: возможности, которые отличают компанию от конкурентов и создают конкурентное преимущество. Они заслуживают повышенных вложений в моделирование и разработку.
  • Поддерживающие поддомены: необходимые для работы бизнеса возможности со специфическими требованиями компании, которые непосредственно не создают её ключевого конкурентного преимущества.
  • Универсальные поддомены: возможности, для которых отрасль уже предлагает зрелые решения, обычно доступные для покупки или повторного использования.

Классификация зависит от бизнеса. Аутентификация может быть универсальным поддоменом для магазина, но ключевым для поставщика сервисов идентификации. Использование возможности несколькими модулями само по себе не делает её универсальным поддоменом.

Единый язык Ubiquitous Language

Эксперты и разработчики создают и постоянно используют общий язык внутри ограниченного контекста, сохраняя одинаковый смысл терминов в обсуждениях, документации и коде. Например, нужно согласовать, требует ли «заказ оплачен» полной оплаты и когда допускается отмена. Язык согласован внутри своего контекста; единая модель для всей компании не обязательна.

Ограниченный контекст

Ограниченный контекст (Bounded Context) задаёт область, в которой конкретная доменная модель и её язык остаются согласованными и применимыми. Поддомены разделяют бизнес-проблемы, а контексты определяют границы моделей. Их нельзя считать фиксированной иерархией вложенности. Желательно ясное соответствие, но на проектирование также влияют существующие системы и организационные ограничения.

Например, «товар» в контексте продаж может описывать цену и скидки, а в складском контексте — место хранения и правила движения запасов. Модели можно связать идентификатором товара, не используя один общий класс со всеми атрибутами и поведением.

Сквозной бизнес-процесс может пересекать несколько контекстов: контексты заказов, платежей и исполнения совместно обеспечивают оформление, оплату и отгрузку. Карта контекстов (Context Map) явно описывает отношения и контракты интеграции. При необходимости антикоррупционный слой преобразует внешнюю модель и защищает собственную.

Ограниченные контексты помогают определять границы сервисов, но не равны единицам развёртывания. DDD применим и к модульному монолиту, и к микросервисам. Один контекст может реализовываться несколькими модулями или физическими сервисами; соответствие «один контекст — один микросервис» не является обязательным правилом.

Понятия тактического проектирования

Объекты-значения и сущности

Различие зависит от того, нужно ли бизнесу отслеживать идентичность объекта во времени:

  • Сущность (Entity): доменный объект, определяемый идентичностью. Изменение суммы или статуса не создаёт новый заказ, если его идентификатор сохраняется. Заказы с разными идентификаторами различны, даже когда суммы совпадают.
  • Объект-значение (Value Object): не имеет самостоятельной идентичности, которую нужно отслеживать, и сравнивается по значимым для бизнеса значениям. Денежные объекты с одинаковой суммой и валютой взаимозаменяемы; при разных валютах они не равны.

Объекты-значения обычно делают неизменяемыми: старое значение заменяют новым, предотвращая случайные изменения через общие ссылки. Сущности также могут иметь неизменяемую реализацию. Равенство определяется идентичностью или значениями, а не изменяемостью. Одно понятие может моделироваться по-разному в разных контекстах.

В сложной области сущности, объекты-значения и корни агрегатов должны инкапсулировать принадлежащие им бизнес-правила. Поведение «отменить заказ» может проверять допустимость отмены, вместо того чтобы разрешать вызывающему коду произвольно менять статус. Прикладной слой координирует сценарии, инфраструктура отвечает за доступ к базе и технические детали. Содержательная доменная логика не требует прямого обращения сущности к базе данных.

Объекты только с данными и getter/setter, из которых вынесены все бизнес-правила, обычно характеризуют анемичную доменную модель. Это не универсальная рекомендация для сложного моделирования, хотя простые CRUD-сценарии могут использовать простую модель данных. POJO означает обычный Java-объект и не ограничивает наличие бизнес-логики.

Репозиторий Repository

Репозиторий предоставляет доступ к доменным объектам как к коллекции, скрывая запросы, отображение объектов и детали хранения. В DDD репозитории обычно проектируют вокруг корней агрегатов, требующих сохранения и прямого доступа, а не для каждой таблицы или внутренней сущности.

Интерфейс должен ясно выражать доменный смысл: например, загрузить заказ по идентификатору или сохранить агрегат заказа. Краткость имён методов не является критерием проектирования. Не следует напрямую открывать доменному слою поля базы и произвольные обновления отдельных таблиц.

Репозиторий можно реализовать с помощью MyBatis. Способен ли Mapper непосредственно выполнять его роль, зависит от требований к загрузке, сохранению и инкапсуляции агрегатов. Обычный CRUD Mapper одной таблицы не становится DDD-репозиторием только потому, что предоставляет операции хранения.

Агрегаты и их корни

Агрегат (Aggregate) — граница транзакционной согласованности, защищающая бизнес-инварианты. Инварианты — правила, которые должны выполняться при каждой фиксации бизнес-изменения. Агрегат может содержать несколько сущностей и объектов-значений или только корневую сущность. Корень агрегата сам является сущностью, служит точкой доступа и защищает внутренние правила.

Допустим, модель требует, чтобы общая сумма заказа всегда равнялась сумме его позиций. Заказ и позиции можно объединить в агрегат, где корень контролирует изменения позиций. Границу задают требования согласованности, а не предположение, что все связанные объекты необходимо объединить.

  • Внешний код должен изменять состояние через поведение корня агрегата, не меняя внутренние сущности напрямую в обход бизнес-правил. Неизменяемые объекты-значения можно безопасно возвращать вызывающему коду.
  • При фиксации бизнес-изменения должны выполняться правила немедленной согласованности: например, положительное количество в позиции и правильный расчёт суммы. При сохранении также необходимы подходящие транзакции и контроль конкурентного доступа.
  • Между агрегатами обычно предпочтительны ссылки по идентификатору. Их взаимодействие можно организовать через доменные события и согласованность в конечном счёте. Более строгая согласованность определяется требованиями бизнеса; наличие связи не требует объединения всех объектов.

«Загрузить, изменить, сохранить» описывает сценарий, но не означает, что каждое чтение обязательно включается в транзакцию базы, охватывающую весь процесс. Границы агрегата выражают требования согласованности; конкретные стратегии транзакций и конкурентного доступа выбирают при реализации.

Доменные события Domain Events

Доменное событие выражает уже произошедший и значимый для бизнеса факт, например «заказ отменён». События определяют по бизнес-смыслу и правилам; произвольное изменение атрибута сущности не порождает их автоматически. Имена обычно отражают прошедшее время, а содержащиеся факты остаются неизменными.

Внутренние доменные события можно обрабатывать синхронно или асинхронно внутри процесса. Для взаимодействия контекстов факты успешно зафиксированных операций обычно преобразуют в интеграционные события и передают другим сервисам через систему сообщений. Очередь — выбор реализации, а не часть определения доменного события.

Например, отмена заказа может уведомить складской контекст об освобождении зарезервированных запасов. Запись «отмена произошла» нельзя переписать так, будто её не было, но бизнес допускает последующие действия или компенсацию. Неизменяемость события не означает невозможность скорректировать любой бизнес-результат.

Доменный сервис Domain Service

Важную доменную операцию, которая естественным образом не принадлежит сущности или объекту-значению, можно моделировать доменным сервисом. Например, в модели, учитывающей место назначения, способ доставки и предложения перевозчиков, таким сервисом может быть расчёт доставки. Критерий — принадлежность поведения, а не количество участвующих объектов.

Доменные сервисы обычно не хранят собственное состояние и используют единый язык для названий и операций. Если добавление позиции естественно принадлежит агрегату заказа, его можно реализовать поведением заказа без отдельного сервиса синхронизации состояния.

Прикладной сервис принимает запрос сценария, загружает агрегаты, вызывает доменное поведение и координирует сохранение и транзакции. Доменный сервис реализует бизнес-правила, не принадлежащие другим доменным объектам. Ни один из этих терминов не равнозначен отдельно развёрнутому микросервису.

Доменное моделирование в DDD

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

  1. Обсудить с экспертами типичные бизнес-сценарии, правила и цели; сформировать единый язык и первоначальные поддомены.
  2. Определить, где модель остаётся согласованной, выделить ограниченные контексты и описать их взаимодействие и контракты интеграции.
  3. В каждом контексте выявить сущности, объекты-значения и инварианты; на их основе спроектировать границы агрегатов, корни и поведение.
  4. По необходимости спроектировать доменные сервисы, события, репозитории и создание объектов; определить обязанности прикладного слоя и инфраструктуры.
  5. Проверять модель на конкретных сценариях и ключевых правилах. При изменении понимания бизнеса совместно уточнять код, язык и границы моделей.

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

Источники

Комментарии

Присоединяйтесь к обсуждению с аккаунтом GitHub. Все языковые версии используют общие комментарии.

Введите Главная, Новости или Передовые исследования для перехода или ищите статьи.

↑↓ Выбрать ↵ Открыть Esc Закрыть