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

Понятия стратегического проектирования
Предметная область
Предметная область — это область задач, которые должна решать система. Например, ею может быть управление информацией о товарах.
Подобласти
По различиям используемого языка предметную область можно разделить на подобласти:
- Ключевая подобласть определяет основное конкурентное преимущество продукта. Это наиболее важная, центральная и специфическая часть бизнеса.
- Общая подобласть предоставляет универсальные функции нескольким подобластям. Примеры — общие системы аутентификации и управления правами. Они не ограничены особенностями предприятия и не требуют большой адаптации.
- Вспомогательная подобласть не содержит ни ключевых, ни универсальных функций. Она специфична для предприятия и не является общей; пример — системы справочников с кодами данных.
Ограниченный контекст
Ограниченный контекст определяет область, внутри которой используется конкретная модель для решения соответствующих задач.
Ограниченный контекст объединяет одну или несколько подобластей, Нужно обеспечить поддержку полного бизнес-процесса одним ограниченным контекстом, и включить в него все предметные области, задействованные в этом процессе. Ограниченный контекст служит основанием для выделения микросервисов: каждому контексту соответствует один микросервис.
Зачем вводить ограниченные контексты? Потому что контекст усложняет написание кода. Рассмотрим пример:
В подборе персонала «платформа» может обозначать учебное заведение или организацию. В прыжках в воду это вышка, с которой прыгает спортсмен. На железной дороге это посадочная платформа. Представим, что однажды вместе оказались прыгун в воду, HR и проводник, не зная профессий друг друга. И HR спросил спортсмена: «С какой вы платформы?»…
Видите? Все трое используют слово «платформа», но без предварительной договорённости могут понимать его по-разному. Решение — разделить области прыжков в воду, найма и транспорта и определить термины внутри каждой. Обсуждение в пределах одной области максимально уменьшает неоднозначность. Это и есть договорённость. По той же причине связанные с бизнесом области лучше определить в одном ограниченном контексте: чтобы уменьшить неоднозначность.
Аналогичная проблема возникает при проектировании ПО. Допустим, большой продукт охватывает прыжки в воду, найм и поездки, но сервисы никак не разделены. В логике прыжков структура platform означает вышку, в найме — организационную платформу, в транспорте — перрон. Когда три направления неизбежно пересекутся, начнётся беда: что делать с экраном, заполненным почти одинаковыми именами переменных с разным смыслом?
Если точно ограничить смысл термина в предметной области, внутри неё его можно применять свободно. В этом и состоит важная роль ограниченного контекста.
Понятия тактического проектирования
Объекты-значения и сущности
Сначала познакомимся с объектами-значениями и сущностями.
- Объект-значение определяется значениями своих свойств: два таких объекта с одинаковыми внутренними значениями считаются равными. Следовательно, свойства объекта-значения неизменяемы.
- Сущность — бизнес-объект с уникальным идентификатором, состоянием и жизненным циклом. Её свойства изменяемы. Даже полностью одинаковые свойства не делают две сущности одной и той же: совпасть должны их идентификаторы, например ID.
Различим эти понятия на примере:
Предположим, у нас есть объект-значение белого цвета Color white{R:255, G:255, B:255} и сущность шины tire{Air:,Size:}:
- Изменяемость: каждое свойство белого цвета неизменно, потому что после его изменения объект уже не обозначает белый цвет. Давление, размер и другие параметры шины могут меняться: она всё равно остаётся шиной.
- Сравнимость: неизменяемость значений определяет правило равенства объектов-значений. Значения двух объектов белого цвета обязательно совпадают, поэтому объекты с одинаковыми внутренними значениями считаются равными. У сущностей из-за изменяемости свойств такого свойства нет.
У сущностей обычно выделяют четыре формы модели:
- Модель без поведения содержит только данные и методы getter/setter. Вся бизнес-логика и прикладная логика размещается в сервисном слое. В Java такой класс называют POJO.
- Анемичная модель включает часть бизнес-логики, но не ту, что зависит от слоя хранения. Логика, зависящая от хранения, находится в сервисном слое.
- Богатая модель содержит всю бизнес-логику, включая зависимую от слоя хранения.
- Перегруженная модель помещает в предметную модель и другую прикладную логику, не относящуюся к бизнес-логике, например авторизацию и транзакции.
Репозиторий Repo
Repo определяет операции хранения для Entity. Операции должны быть по возможности низкоуровневыми, иметь короткие имена и не быть слишком многочисленными. MyBatis Mapper в Java можно рассматривать как реализацию Repo.
Агрегаты и корни агрегатов
Агрегат — инкапсуляция большего масштаба. Он объединяет сущности и объекты-значения с общим жизненным циклом, неразделимые с точки зрения бизнеса. Внешние ссылки разрешено предоставлять только корню агрегата. Агрегат также выражает связность. Корни могут вызывать друг друга; их названия обычно являются существительными.
Корень агрегата обеспечивает инкапсуляцию следующими способами:
- Работать со всем агрегатом нужно через его корень; внешний код не должен напрямую обращаться к внутренним элементам. Например, фиолетовый цвет кузова + шины + стальная рама + … = автомобиль. При вождении мы не управляем отдельно шиной, рулём или внешностью. Мы действуем через автомобиль как корень агрегата, косвенно управляя остальными сущностями и объектами-значениями.
- Агрегат задаёт границу, внутри которой все компоненты должны быть корректны с точки зрения бизнес-логики.
- Работа с агрегатом должна проходить в одной атомарной транзакции, иначе возможны ошибки. Агрегат — единица операции: извлечение из репозитория, изменение и возврат составляют атомарное действие. Например, если автомобиль включает колёса и стальную, алюминиевую или карбоновую раму, нельзя снять колесо для ремонта и после ремонта не установить его обратно.
События предметной области — Domain Events
Изменение свойств сущности порождает событие предметной области — происходящее в ней событие, которое эксперты считают значимым и заслуживающим внимания. Обычно оно означает изменение состояния предметного объекта.События передают сообщения, запускают другие действия и служат важным средством уменьшения зависимостей предметной модели. Для их передачи часто используют очередь сообщений, чтобы каждая подписанная подобласть выполнила собственную внутреннюю реакцию.
Шина сообщений — ещё один способ реализации 👋.
Например, изменение давления в сущности шины порождает событие утечки leaked. Оно передаёт сообщение «шина спускает», вызывая другие реакции: изменение работы силовой системы, ощущений на руле и так далее. Названия событий обычно ставят в прошедшее время, показывая, что событие уже произошло и необратимо.
Сервис предметной области — Domain Service
Некоторые действия предметной области, по-видимому, не принадлежат никакому объекту. Они выражают важное поведение, поэтому их нельзя игнорировать или просто включить в случайную сущность либо объект-значение. Когда такое поведение выделено, рекомендуют объявить его сервисом предметной области.
Сервис предметной области — не тот же «сервис», что в микросервисах. Сервисы и корни агрегатов отчасти похожи: оба могут работать с несколькими сущностями. Но их идеи и роли различаются. Корень агрегата объединяет сущности, а сервис синхронизирует состояния нескольких сущностей, например добавляет сущность item в сущность list — скажем, доставляет mail в inbox. Поэтому название Service обычно выражается глаголом.
Моделирование предметной области в DDD
Обычная последовательность моделирования:
- На основе требований предварительно выделить подобласти, ограниченные контексты и связи между контекстами.
- Подробно изучить каждый контекст, определить сущности и объекты-значения, а затем выбрать форму модели сущностей: без поведения, анемичную, богатую или перегруженную.
- Связать сущности и объекты-значения в агрегаты, определить их границы и корни.
- Спроектировать repo для корней агрегатов и продумать создание сущностей и объектов-значений.
- Применить модель в проекте, проверить её обоснованность практикой, выявить недостатки и выполнить рефакторинг.
Это двухэтапный подход DDD: сначала стратегическое проектирование, затем тактическое.
На практике рекомендуются сущности без поведения, с одними setter/getter, или анемичные модели с простой логикой без операций базы данных, например проверкой допустимости свойств.
Это лишь один способ моделирования DDD — сверху вниз. Возможен и подход снизу вверх: сначала определить предметные модели, сущности и объекты-значения, а затем подобласти и ограниченные контексты…
(Конец раздела)