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

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

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

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

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

img

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

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

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

Подобласти

По различиям используемого языка предметную область можно разделить на подобласти:

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

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

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

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

Зачем вводить ограниченные контексты? Потому что контекст усложняет написание кода. Рассмотрим пример:

В подборе персонала «платформа» может обозначать учебное заведение или организацию. В прыжках в воду это вышка, с которой прыгает спортсмен. На железной дороге это посадочная платформа. Представим, что однажды вместе оказались прыгун в воду, 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

Обычная последовательность моделирования:

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

Это двухэтапный подход DDD: сначала стратегическое проектирование, затем тактическое.

На практике рекомендуются сущности без поведения, с одними setter/getter, или анемичные модели с простой логикой без операций базы данных, например проверкой допустимости свойств.

Это лишь один способ моделирования DDD — сверху вниз. Возможен и подход снизу вверх: сначала определить предметные модели, сущности и объекты-значения, а затем подобласти и ограниченные контексты…

(Конец раздела)

comments powered by Disqus