Цикл статей написан для java-разработчиков, создающих системы со сложной предметной областью. Система, как правило, представлена сетью микросервисов или модульным монолитом. В рамках такой системы обычно содержится большое количество сущностей (JPA/JOOQ), которые одновременно используют как технические, так и бизнес-сущности. Смена технологической базы по прошествии нескольких лет разработки для такой системы является очень дорогостоящим решением. Доработки бизнес-функционала занимают большое количество времени.
В первой части цикла статей проведем теоретический обзор основных терминов и паттернов, на следующей неделе поговорим о гексагональной архитектуре, а в заключении будет представлен пример реализации technology agnostic кода с применением основных паттернов DDD. В результате будет спроектирован DDD Aggregate, чье состояние на уровне БД будет извлекаться с помощью JOOQ, а внесение изменений происходить с помощью JPA.
Какие проблемы вы сможете решить после прочтения:
Несоблюдение принципа DIP при разработке enterprise приложений.
Распределение бизнес-логики одного business процесса по нескольким классам в рамках одного приложения.
Негативные эффекты от наличия bidirectional связи между JPA сущностями.
Негативные эффекты от READ операций, реализованных с помощью JPA.
Во многих приложениях, написанных на Java, по прошествии нескольких лет разработки, код приложения становится спагетти-кодом. Спагетти-кодом называют код, который не следует принципам: KISS, YAGNI, DRY, TDA, SOC.
Это приводит к ряду проблем:
Логика приложения становится сложной, так как код бизнес-логики распределен по нескольким сервисным классам. Это усложняет процесс ее чтения.
Скорее всего, результат вас не порадует: между программистом и специалистом предметной области не всегда есть полное понимание реализуемого бизнес-процесса. Доменная модель приложения, чаще всего выражена JPA сущностями, представляет собой сложно поддерживаемый набор классов. Этот набор не всегда правильно отражает видение пользователями сценариев бизнес-процессов и представлений о вовлеченных объектах.
Потере стабильности логики приложения с точки зрения работы заявленного функционала, так как она не обложена модульным и интеграционным тестированием. Причина отсутствия тестов, как правило, кроется в том, что классы, чьи методы нужно протестировать, содержат большое количество зависимостей с различных уровней приложения, что усложняет реализацию тестов или делает бессмысленным тестирование отдельных сервисных компонент, так как отдельный компонент всегда содержит не всю бизнес-логику, а только ее часть.
Пропуску инвариантов объекта доменного уровня при реализации кода бизнес-задания, что приводит к частому возникновению багов и сложности их локализации и устранения.
Что такое DDD и как он помогает в реализации бизнес-логики?
Ключевым термином DDD является домен. Домен — это набор действий или процессов, описанный доменными экспертами. В совокупности эти действия представляют собой область или отрасль, в которой работает компания. Домен может состоять из множества частей, каждая из которых представляет собой логически и семантически объединенную группу процессов.
В качестве примера домена возьмем магазин книг. Магазин книг занимается продажей книг, наймом сотрудников магазина, выплатой зарплат сотрудникам, арендой помещения под магазин, поддержкой сайта, планированием бонусных и скидочных акций. Каждое из перечисленных действий выполняется экспертом, который разбирается в этом.
Действия делятся на 2 типа:
Действие – часть основного бизнес-процесса;
Действие, которое поддерживает часть основного бизнес-процесса.
Действия группируются в поддомены.
Каждый поддомен является частью problem space — одним из наборов задач, решение которых в дальнейшем нужно реализовать в коде. Само же решение-реализация является solution space. Результатом применения стратегических паттернов DDD является определение problem space, тактических паттернов DDD — определение solution space.
Существует 3 типа поддоменов:
Основной (core) поддомен.
Содержит в себе задачи для реализации основных задач компании. В случае с магазином книг это продажа.
Вспомогательный (support) поддомен.
Содержит в себе второстепенные задачи для того, чтобы задачи из основного домена могли функционировать. В нашем примере — это получение книг в ассортимент магазина.
Общий (generic) поддомен.
Содержит в себе общие задачи для любого вида бизнеса. Как в случае с магазином книг, так и в случае с любым другим видом бизнеса, набор сотрудников, оформление скидок — являются общими задачами.
Каждый поддомен – это часть модели домена бизнеса, где функционал, расположенный в поддомене, содержит собственный набор бизнес-логики, реализующий целевые задачи этого домена. Для грамотного проектирования домена и дальнейшего декомпозирования в поддомены, в процессе проектирования, обязательно должны присутствовать не только технические специалисты, но и специалисты предметной области. А начинать процесс проектирования следует с методики event storming.
Результатом правильного проектирования и реализации DDD являются 4 свойства бизнес-логики:
легко понять;
легко поддерживать;
легко тестировать;
не зависит от технологических фреймворков.
Домен и стек технологий
При проектировании домена силами только программистов, часто происходит восприятие проекта как набора технологий, а не как результата в виде выполнения бизнес-процесса. С точки зрения DDD, это неверный подход. Первоочередным этапом является моделирование предметной области.
DDD ориентирован на работу с гибкими методологиями, например, SCRUM. Для моделирования домена в процессе разработки следует использовать методику event storming. Во время event storming, помимо визуализации домена и содержащихся в нем бизнес-процессов, формируется общий язык обсуждения и моделирования домена – единый язык/Ubiquitous Language.
Контекст предметной области DDD
После завершения стратегического этапа анализа и моделирования домена приложения, мы получаем модель, разделенную на поддомены, где каждый поддомен является контекстом предметной области.
Контекст предметной области является стратегическим шаблоном DDD. Его цель – поддержание согласованности между поддоменом и бизнес-моделью, которую он выражает. В нескольких поддоменах может содержаться сущность домена с одним и тем же названием, но разным набором атрибутов, т. к. в разных моделях данная сущность может по-разному восприниматься в зависимости от контекста поддомена.
Контекст предметной области характеризуется следующими свойствами:
Каждый контекст должен обладать своей моделью домена.
В контексте должна быть представлена структура сущности домена и доменные события, способы взаимодействия с другими ограниченными контекстами.
На уровне каждого контекста предметной области имеется свой набор терминов, одинаковый для разработчиков и аналитиков и формирующийся в единый язык.
Модель домена, определенная на уровне контекста первой предметной области, не переиспользуется в другом контексте.
Примером плохого деления домена на контексты бизнес-областей часто является частая коммуникация с другими микросервисами в рамках бизнес-процессов, реализованных в микросервисе.
При правильном проектировании и реализации:
Достигается низкая связанность между контекстами предметных областей.
Высокая связанность компонентов внутри каждого контекста.
Результатом становится независимая разработка различных частей приложения, выраженных либо модулями монолита, либо микросервисами.
DDD и код
Код, написанный с использованием методики DDD, отражает доменную модель:
Сценарии бизнес-процессов.
Сущности, на которые воздействуют эти сценарии.
Методы, которые вызываются у этих сущностей и применяются для достижения бизнес-результата.
DDD агрегат
DDD агрегат — группа объектов, которые должны быть всегда в консистентном состоянии.
Итак, давайте разберемся, что нам дает определение DDD агрегата в рамках разработки java enterprise и как это использовать.
Для начала разберемся с тем, что такое агрегат в терминологии DDD.
Агрегат — это шаблон, направленный на:
структурирование бизнес-логики таким образом, чтобы все инварианты (critical business rules) доменной модели приложения были привязаны к соответствующим сущностям этой модели, где доменная сущность выражена чистым java кодом;
обновление одного и только одного агрегата в рамках одной локальной транзакции;
изменение состояния агрегата на уровне бизнес-логики, которое возможно только через методы корня агрегата;
изменение состояния графа JPA сущностей, сопоставленных графу домен сущностей. Что также возможно только через JPA repository сущности, сопоставленной корню агрегата;
отсутствие межагрегатных ссылок при разработке микросервисов, что приводит в локализации ACID транзакций на уровне микросервиса.
DDD Entity
DDD entity – это представление бизнес-объекта, полученного в результате тактического анализа одной из частей контекста предметной области. Это представление выражается кодом в приложении. Примером DDD entity в рамках магазина книг является книга или автор.
Сущность характеризуется следующими свойствами:
У сущности должен быть уникальный идентификатор, не изменяемый на всем протяжении жизненного цикла.
Сущности не должны иметь сеттер-методы (setter).
Конструктор (builder) сущности должен являться единственным способом ее создания.
Сущность должна иметь смысл только в рамках одного контекста.
Сущность с таким набором атрибутов должна иметь значение только в одном контексте предметной области.
Сущность должна содержать методы валидации инвариантов своего состояния.
Сущность должна инкапсулировать бизнес-логику.
Две сущности с разным значением полей рассматриваются как один и тот же объект домена, если их идентификаторы равны.
Максимум бизнес-логики следует инкапсулировать в сущность домена, чтобы:
Можно было сделать инкапсуляцию данных.
Стремиться к обеспечению большинства мутаций состояния сущности на основании бизнес-контекста методами сущности.
DDD Корень Агрегата
Это корневая DDD сущность, через которую проходит большинство бизнес-операций с графом DDD сущности агрегата. Эта сущность содержит методы оркестрации ключевых бизнес-правил в текущем контексте предметной области. Прямая оркестрация бизнес-логикой агрегата должна быть распределена между телом класса корня агрегата и телом класса сервиса, относящимся к корню агрегата. Условием нахождения бизнес-логики в сервисе является то, что часть бизнес-логики бизнес-процесса семантически не удовлетворяет условиям нахождения в корне агрегата.
Бизнес-логика состоит из:
бизнес-логики мутации состояния DDD сущности;
бизнес-логики валидации состояния DDD сущности;
бизнес-логики создания DDD сущности.
При этом:
За последовательную мутацию данных графа текущего агрегата отвечают методы, расположенные в теле класса корня агрегата.
За создание объектов DDD сущности не корней агрегата отвечает сама DDD сущность через метод конструктора.
Методы валидации инвариантов сущности предоставляют корень агрегата и сами сущности. С учетом этого, методы валидации сущностей вызываются в теле методов агрегата.
Объект-значение в DDD
Объект-значение в DDD (DDD Value Object) не является сущностью. Он является её элементом, служит для передачи данных и обладает следующими характеристиками:
Может переиспользоваться в нескольких контекстах предметной области.
В случае переиспользования, объект-значение является идентификатором (identity) сущности и часто выступает в качестве межагрегатной ссылки.
Не содержит сеттеров (setter методы).
При необходимости может содержать геттеры (getter методы).
Немутабельный.
Всегда создается через конструктор (builder).
Мутабельность DDD сущности и объекта-значения
Объекты-значения содержат только значения, у них нет идентификатора. Таким образом, можно взаимозаменяемо использовать один и тот же объект-значение в разных местах, поскольку он имеет только значение без идентификатора.
Возможность повторного использования появляется при создании объекта-значения в неизменяемом формате, так как после установки значения оно не может быть изменено и может использоваться в нескольких местах без побочных эффектов. Для изменения значения нужно создать новый экземпляр вместо обновления значения.
С другой стороны, DDD сущность имеет уникальный идентификатор, который присваивается и остается неизменным на протяжении всего срока службы объекта. Две DDD сущности с одинаковым идентификатором считаются одним и тем же объектом, даже если все остальные свойства различны. Аналогично DDD сущности считаются разными, если их идентификаторы различны, даже если все остальные свойства одинаковы. Сущности изменяемы, потому что они используются для запуска бизнес-логики и применения и их свойства необходимо обновлять на основе бизнес-правил. Таким образом, у DDD сущности должны быть методы изменения состояния.
Надеюсь, материал был для вас полезен. Подписывайтесь, чтобы не пропустить следующие выпуски. Буду рад ответить на вопросы в комментариях к статье.