Domain-Driven Design: Это чё хоть такое-то?
Лобов КонстантинЧто такое DDD - как расшифровывается каждая из букв?
D is for Domain - Предметная область. К примеру, Банковская сфера или Добыча нефти или Интернет-торговля, или Баг-трекинг.
D is for -Driven - "На основе" - ну, без комментариев
D is for Design - проектирование в широком смысле слова - аналитика и проработка архитектуры как перед началом разработки, так и в её процессе.
Итого: Предметно-Ориентированное Проектирование (также встречается Проблемно-Ориентированное Проектирование). Проектирование, основой для которого служит предметная область.
В чем суть DDD?
Во главу процесса разработки ставится предметная область, которая пронизывает все аспекты системы - от взаимодействия в команде до API и UI.
Предметно-ориентированное проектирование — это не конкретная технология или методология. DDD — это набор правил, которые позволяют принимать правильные проектные решения.
DDD лежит на стыке agile-методологий и объектно-ориентированных подходов к разработке, строится на лучших практиках разработки enterprise-приложений и идеях аспектно-ориентированной разработки. Является "клеем" между людьми и архитектурными паттернами.
Что такое Домен и Модель?
Домен, он же Предметная область - это знания и концепции о части реального мира, с которой нам предстоит работать или моделировать в нашем ПО.
Модель - это описание сущности предметной области в той мере, которая будет полезна при разработке нашего ПО. Можно представить как класс, содержащий поля-свойства реального объекта, методы - бизнес-правила и связи с другими смежными моделями.
Пример домена - Интернет-торговля, пример модели - Заказ, который состоит из товаров, имеет Покупателя и Адрес доставки, а так же может быть доставлен либо отменен.
Еще пример - Багтрекинг. Модели: Задача, Пользователь, Исполнитель, Наблюдатель.
Как возник DDD, какие проблемы призван решить?
Пример задачи - начать разработку большого enterprise-проекта с нуля.
Какие задачи встают при проектировании:
- Как построить взаимодействие с заказчиком и внутри команды?
- Как реализовать MVP и не увязнуть в легаси?
- Как спроектировать гибкую и предсказуемо масштабируемую архитектуру?
- Как перенести знания специалистов предметной области в код, API и UI?
Для решения этих (и не только этих) задач DDD предлагает ряд практик.
Из чего состоит DDD
1. Единый язык
2. Контекст и Ограниченный контекст
3. Агрегация и Корни агрегации
1. Единый язык (Ubiquitous language)
Единый язык - это словарь сущностей и связей предметной области.
Язык должен стать основой общения между всеми участниками разработки. Он должен быть понятен всем и не должен допускать двояких трактовок.
Требования к Языку: естественный язык (русский с однозначным переводом на английский), непротиворечивость, минимальная достаточность для описания предметной области.
Для формального описания на этапе проектирования может быть использована любая удобная нотация - ER, UML, даже IDEF подойдет (IDEF0/IDEF4). Важное требование - все участники должны уметь "читать" такую модель.
Для базового описания моделей обычно достаточно описать сущности с их полями, а так же расставить связи между ними, используя глаголы
- Является (наследование),
- Связан с (ассоциация),
- Содержит (агрегация),
- Состоит из (композиция).
При переходе от проектирования к разработке каждая модель однозначно переносится в код в виде классов и связей с использованием инструментальных средств ORM или схожих техник.
Пример из предметной области "Багтрекер".
Сущность Баг
Является Задачей
Состоит из Названия, Описания, Оценки по времени.
Ссылается на Создателя (Пользователь), Исполнителя (Пользователь), Наблюдателей (Пользователи).
Содержит Комментарии.
Вызовы: создание языка - тяжелый труд для всех участников процесса, требует детальных знаний предметной области всеми участниками процесса и времени на его создание, а так же контроля эволюции языка (дистилляция знаний).
Плюсы: если проект нов для участников разработки, то создание общего языка - хороший способ обучить людей всем нюансам предметной области. В случае, если имеются неявные пробелы в знаниях о предметной области, то они вскроются на ранних стадиях проекта.
(отсебятина) Вообще, можно внедрить командную практику в переписке, документации и комментариях ПО именовать все сущности с большой буквы - это упрощает понимание и укрепляет общий язык.
2. Ограниченный контекст
Ограниченный контекст - это предметная область внутри предметной области. Выделенная группа сущностей и связей, служащая одной задаче или выполняющая одну функцию. Сущности и связи в рамках одного ограниченного контекста не могут быть использованы в рамках другого, контексты независимы друг от друга, но могут быть вложены (см Агрегацию).
Пример из предметной области "Багтрекер".
Возможные контексты: Права доступа, История изменений, Трекинг затраченного времени, Отчеты.
Опишем Трекинг затраченного времени:
Трекинг по задаче описывает затраченное Пользователем время по определенной Задаче в рамках одного из Типов активностей.
Контекст включает сущности Задача, Пользователь, Активность по задаче, Тип активности.
Для того, чтобы изолировать контекст, "уточним" общие для нескольких контекстов термины Пользователь и Задача:
Трекинг Активности описывает затраченное Исполнителем время по определенной Активности в рамках одного из Типов активностей.
В данном примере разделение на контексты, возможно, излишне, так как предметная область не настолько велика и новые термины "Активность" и "Исполнитель" выглядят излишними. Но, думаю, общий принцип понятен.
Контексты полезны по нескольким причинам:
1. Изолируют сложность, позволяя охватить в голове проблему целиком
2. Позволяют изолировать неясность - в случае, если еще не до конца понятно, как должна работать какая-либо функция системы, то Контекст позволит изолировать такую неясность
3. Структурируют код, добавляя в него еще одно измерение, в котором можно разбивать/рефакторить/переиспользовать код
4. Контекст - отличный претендент для реализации микросервиса либо службы в рамках разрабатываемого ПО
Проблемы при выделении контекстов:
1. Выделить функциональную область бывает зачастую сложнее, чем построить общий язык. Требуется серьезная аналитика с учетом специфики возможного использования ПО
2. Зачастую одна и та же сущность может разными свойствами и функциями находиться в разных контекстах - в этом случае необходимо правильно рассекать сущность и её содержимое. === Пример ===. Если вы корректно разбили предметную область на контексты, то на каждый Контекст будет приходиться ровно одна такая рассеченная сущность (см. про Агрегацию дальше).
+/- 3. Агрегация и Корни агрегации.
Изолированные Контексты - уже хорошее начало, но их совокупность еще не является полноценным приложением. Требуется связать все Контексты воедино, избежав сильного зацепления и обеспечив их максимальную изоляцию. Для этого в каждом Контексте производится поиск его Корня агрегации:
Строится диаграмма сущностей и связей и ищется такая сущность, которая завязывает на себя максимальное число остальных - к примеру, является суперклассом (parent) к остальным, либо включает в себя (содержит ссылки в своих полях) на большое число типов других сущностей своего контекста.
Зачастую данная сущность является одним из тех объектов, который мы получили при разбиении одной сущности на несколько Контекстов. Скорее всего сущность будет иметь довольно общее название вроде Клиент или Заказ или Продукт и т.д.
Следующее обязательное условие - наделение корневой сущности ключом, однозначно её идентифицирующим. После того, как мы получили механизм идентификации, у нас появляется возможность использовать данный ID для обмена информацией между контекстами. Передача ключа - единственный легальный способ взаимосвязи Контекстов и объединения их в еще большие Контексты. По сути, о Контексте извне неизвестно ничего, кроме ключей на её Корни агрегации.
В случае, если на роль Корня агрегации претендуют несколько объектов, то обычно либо
1. Включают один Корень агрегации в состав другого, преобразуя первый в Ограниченный контекст
2. Разбивают Ограниченный контекст на два независимых
=== Пример диаграммы классов с объединением в СК и КА для каждого ===
Сложности:
* Очень сложно найти хороший Корень агрегации так, чтобы Контекст оказался не слишком большим либо слишком маленьким.
* Порой приходится "перетряхивать" всю предметную область для удовлетворения этому требованию. Есть искушение объявить несколько Корней в одном Контексте.
Плюсы:
* Корни агрегации позволяют удостовериться, что Ограниченные контексты выбраны правильно
* Выделение Корней агрегации позволяет добиться сильной связности при слабом зацеплении
Что в итоге
Конечная цель проектирования по DDD - описание предметной области на языке сущностей и связей с разбиением их на связанные контексты и выделение корней агрегации.
На практике
- Единый язык - самый первый и наиболее заметный для всех участников разработки плюс. Он не допускает разного трактования и недопонимания участниками команды предметных терминов и упрощает внутрикомандное общение и обучение новых участников.
- Предсказуемое масштабирование. В случае, если внутренняя структура приложения выстроена на основе реальной предметной области, изменение требований или расширение функциональности скорее всего органично найдут отражение в доменной модели и позволят минимальными затратами внести изменения в код.
- Слабое зацепление дает возможность разбить систему на максимально независимые модули - идеально для микросервисной архитектуры и аутсорса частей системы, упрощает внесение изменений и позволяет при разработке концентрироваться и держать в голове весь аспект разрабатываемой системы целиком.
- Проектирование REST API заметно упрощается при данном подходе. Фактически каждая модель и каждый контекст могут быть транслированы в endpoint - уже готовы имена и перечень полей для таких ресурсов. В случае, если разрабатываемая система основана на тяжелых бизнес-транзакциях, также хорошо зарекомендовал себя CQRS подход.
- Гораздо проще проектировать логичные пользовательские интерфейсы при наличии органичной доменной модели. Опытным путем замечено, что сущности в рамках одного контекста обычно группируются и в пользовательском интерфейсе - либо на одном экране/вкладке/блоке/странице, либо в рамках одного процесса или user story. Поход также в какой-то мере позволяет проконтролировать отсутствие дублирующихся элементов либо функций в UI.
- Интеграция со сторонними подсистемами: по подобию с выделением микросервисов DDD позволит сделать процесс интеграции более прозрачным и управляемым
- Не стоит забывать, что Доменная модель - это только часть системы. Хорошо, когда Единый язык просачивается на все слои системы. Ничего страшного, если на "границах" системы влияние DDD ослабевает.
Архитектурные практики
По своему опыту внедрение DDD оправдано в случае, когда число классов в системе переваливает за пятьдесят (см.картинку ниже). В противном случае практика несет неоправданное усложнение.
ORM - средоточие знаний о предметной области. Хорошая ORM станет сердцем доменной модели, плохая приведет к фейлу проекта.
Архитектурные паттерны корпоративных приложений (Шаблоны корпоративных приложений, М.Фаулер) - практически, основа для имплементации DDD-подходов.
REST / CQRS - чистый и строгий API, основанный на состояниях объектов и/или бизнес-транзакциях, идеологически продолжает концепции DDD.
Принципы аспектно-ориентированного программирования пересекаются с идеей выделения ограниченных контекстов под каждый аспект предметной области.
+/- Как понять, что в проекте имеются проблемы
* В части кода, описывающей бизнес-логику, присутствуют методы/объекты/директории с названиями Helper, Manager, Wrapper, Service и другие "-er"-ы.
* Модели анемичны - близки к DTO, служат только для хранения данных и не содержат бизнес-логики
* Контексты несуразно различаются размерами
* Группа людей начинает
Возможные примеры:
Кредит
Пакет услуг
Ссылки:
https://martinfowler.com/bliki/BoundedContext.html
https://habrahabr.ru/post/232881/
https://habrahabr.ru/post/313110/
https://akrabat.com/wp-content/uploads/2017-01-11-CodeMash-DDD-for-beginners.pdf
http://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_1.pdf
http://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdf
http://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_3.pdf