Введение
Привет, Хабр! После того, как я углубился в мир DDD, прочитав множество книг, я решил поделиться с вами своими мыслями и наработками, которые, я надеюсь, окажутся полезными для разработки эффективных приложений вашим командам.
В данной статье мы погрузимся в мир DDD, сфокусировавшись на самом важном аспекте – модульности. Разберем стратегические паттерны, предоставляющие необходимые инструменты для эффективной организации модульности на уровне организации. Обсудим, как определить границы между контекстами, установить правила взаимодействия и эффективно управлять сложностью в разработке крупных бизнес-приложений.
Модульность
Рано или поздно каждый из нас сталкивается с необходимостью разделить что-то сложное на более мелкие, управляемые части. Это может быть вызвано желанием упростить задачу или необходимостью эффективного распределения ответственности.
В рамках крупных компаний с множеством функциональных команд часто встает вопрос о том, как оптимально распределить обязанности между ними.
Разделение ответственности следует проводить таким образом, чтобы минимизировать взаимодействие между разными частями системы. Это позволяет скрыть сложность за абстракциями и предоставить простой и понятный интерфейс для взаимодействия.
В контексте разработки программного обеспечения подобные принципы связаны с идеей модульности. Если цель — избежать создания сложного и трудно поддерживаемого кода, необходима четкая и модульная структура проекта. Но что, если требуется обеспечивать модульность в организации бизнес-процессов?
Здесь на помощь приходят стратегические паттерны DDD (Domain-Driven Design), предоставляя ценные рекомендации и решения для управления сложностью, абстрагирования различных аспектов бизнеса и установления четких границ между областями ответственности.
Domain и Subdomain
Домен (Domain) представляет собой нечто большее, чем просто техническое определение. В контексте разработки, это сфера деятельности или область знаний, тесно связанная с бизнес-процессами, которые ваше приложение воплощает. Рассмотрим пример: если ваш проект – интернет-магазин, то доменом будет охватываться весь цикл бизнес-процессов, начиная от управления продуктами и заканчивая обработкой заказов. Это абстрактное пространство, где технические и бизнес-аспекты взаимодействуют для достижения общей цели.
Поддомен (Subdomain) представляет собой уточненную часть более крупного домена, выделенную для детального анализа и организации. На примере интернет-магазина, поддомены могут включать в себя управление продуктами, обработку заказов, учет клиентов и другие аспекты деятельности. Каждый поддомен фокусируется на конкретной области и предоставляет структурированный подход к ее реализации.
Важно отметить, что каждый поддомен, со временем и с ростом бизнеса, может вырасти в отдельный домен. Большой интернет-магазин, например, может иметь множество подразделений, каждое из которых представляет собой отдельный домен. Однако не все эти домены будут иметь одинаковую степень важности. Некоторые из них будут являться ключевыми (Core domain), в то время как другие могут оставаться вспомогательными.
Целевая структура в случае поддоменов напоминает матрешку, где каждая внутренняя часть представляет собой более узкую, специализированную область, а внешние слои представляют более широкие и общие области. Это значит, что каждый поддомен может выступать доменом для своих дочерних поддоменов.
Bounded context
Теперь, когда мы имеем представление о том, что такое домен и поддомен, настало время глубже погрузиться в мир ограниченных контекстов, ключевого элемента методологии Domain-Driven Design (DDD). Ограниченные контексты — это стратегический инструмент, который помогает эффективно управлять сложностью внутри наших систем.
Представим, что у нас есть система управления персоналом в интернет-магазине. Этот домен включает в себя несколько подобластей:
Управление сотрудниками: Включает в себя информацию о сотрудниках, их персональные данные, навыки, опыт работы и другую связанную информацию.
Управление зарплатой: Отвечает за расчет и выплату заработной платы сотрудникам, учет отработанных часов, вычеты и так далее.
Управление обучением: Занимается отслеживанием учебных программ, пройденных курсов и обучающих мероприятий для сотрудников.
Управление вакансиями: Отвечает за информацию о доступных вакансиях в организации, найм новых сотрудников и процессы отбора.
![](https://habrastorage.org/getpro/habr/upload_files/ece/d0d/f45/eced0df45f26e850fac7594c65178ec1.png)
Допустим, мы решаем внести изменения в механизм расчета заработной платы. В рамках единой системы такие изменения могут потребовать корректировок в общих компонентах, связанных с управлением сотрудниками, обучением и вакансиями. Однако введение общих компонентов там, где они не обязательны, может спровоцировать появление частых ошибок и увеличение сложности процесса тестирования. Кроме того, неправильно определенные границы ответственности могут привести к созданию моделей, аналогичных приведенной ниже:
export class Employee {
id;
name;
skills[];
experience;
experienceRate;
personalDetailsForSalary;
personalDetails;
hourlyWage;
hoursWorked;
addSkill()
upgradeLevel()
addWorkingHours()
calculateWageWithoutTax()
}
Модель сотрудника, как она есть сейчас, включает в себя как навыки, так и количество отработанных часов – это всего лишь малая часть всей информации, которая может потребоваться. Представьте, какой монструозной будет модель спустя некоторое время! Она будет содержать кучу логики из различных областей бизнеса, и разобраться в этом будет ой как непросто. Но что делать? Давайте разделим систему на контексты!
Ограниченные контексты используются для установки логических границ пространства решений предметной области, чтобы лучше управлять сложностью. В DDD ожидается, что каждый ограниченный контекст будет иметь свой единый язык.
Контекст Управления Персоналом — общий контекст, охватывающий все аспекты управления персоналом. Включает в себя основные функции, связанные с сотрудниками.
Контекст Зарплатного Учета — сфокусированный на функциях, связанных с расчетом и выплатой заработной платы, учетом отработанных часов и вычетами.
Контекст Обучения и Развития — сфокусированный на функциях, связанных с обучением, отслеживанием учебных программ и курсов для сотрудников.
Контекст Найма и Отбора — сфокусированный на функциях, связанных с информацией о вакансиях, процессами найма новых сотрудников и отбором кандидатов.
Если при переходе из одной функциональной области в другую вы замечаете, что правила, терминология и модели приобретают уникальные свойства и значения, это является сигналом к рассмотрению возможности установки ограниченных контекстов!
Теперь управление сотрудниками содержит свою модель:
export class Employee {
id;
name;
skills[];
experience; // количество проработанных лет
personalDetails; // детали по сотруднику
addSkill()
upgradeLevel()
}
А контекст управления зарплатой — свою:
export class Employee {
id;
hourlyWage;
hoursWorked;
experience; // коэффициент опыта, на который умножается премия
personalDetails; // детали только для зарплаты
addWorkingHours()
calculateWageWithoutTax()
}
Важно отметить, что вездесущий язык (ubiquitous language) внутри контекста — это модели и связанные с ними поведения и термины. Выбор контекстов зависит от того, какие аспекты системы важны в каждой подобласти и какие правила и модели требуется определить внутри каждого контекста. Ограниченные контексты позволяют избежать избыточной сложности и улучшают модульность системы, делая ее более гибкой и легкой в поддержке.
Subdomains внутри bounded context
Каждый поддомен в рамках ограниченного контекста олицетворяет определенный аспект бизнеса и содержит свои собственные модели, правила и терминологию. Ограниченный контекст лишь определяет область, в которой действуют определенные поддомены, и обеспечивает логическую изоляцию между различными частями предметной области.
Более того, принято выделять типы поддоменов внутри контекста. Определение типа поддомена в контексте означает указание на то, какая роль или функция у поддомена в пределах данного ограниченного контекста. Проще говоря, это определение, какой вклад или задачу выполняет этот поддомен в рамках конкретной области предмета.
Типы поддоменов выделяют следующие:
Core: Относится к поддомену, содержащему ключевые бизнес-процессы и функциональность в пределах конкретного ограниченного контекста. Включает в себя бизнес-логику, которая является основной для данного контекста.
Generic: Поддомен, ориентированный на общность и переиспользование. Содержит компоненты и функциональность, которые могут быть общими для различных контекстов или даже систем.
Supporting: Связан с поддерживающими аспектами, не являющимися ключевыми бизнес-процессами. Включает функциональность, которая может облегчить разработку, но не является основной для бизнеса в данном контексте.
Довольно часто мы можем встретить случаи, когда одни и те же поддомены в разных контекстах имеют разные типы. Давайте рассмотрим наглядные примеры и вернемся к обсуждению. Представим что мы делаем с вами систему управления компетенциями. Определим типы поддоменов:
Управление сотрудниками (Core Subdomain) — здесь будут содержаться ключевые данные о сотрудниках, и их компетенции будут рассматриваться как неотъемлемая часть основных бизнес-процессов.
Управление зарплатой (Supporting Subdomain) — учет компетенций может быть вспомогательным аспектом при расчете заработной платы, и эта информация может быть использована для определения премий или бонусов.
Управление обучением (Core Subdomain) — в данном контексте управления компетенциями, обучение напрямую связано с развитием компетенций сотрудников, и поэтому является основной частью процесса.
Управление вакансиями (Supporting Subdomain) — требования к компетенциям могут быть вспомогательным элементом вакансий, которые могут помочь при подборе подходящих кандидатов.
А теперь представим, что у нас есть ещё система управления персоналом в большой розничной компании, где основной акцент на максимизации продаж. Рассмотрим те же подобласти в этом контексте:
Управление сотрудниками (Core Subdomain) — в данном контексте, где ключевая цель - обеспечение качественного обслуживания клиентов, информация о сотрудниках может быть ключевым элементом основных бизнес-процессов.
Управление зарплатой (Supporting Subdomain) — в контексте розничной торговли управление зарплатой может рассматриваться как вспомогательная функция, обеспечивающая корректные выплаты, но не являющаяся центральным элементом бизнес-стратегии.
Управление обучением (Supporting Subdomain) — обучение персонала может быть важным, но не являться ключевым для розничной компании, где более актуальными могут быть операционные процессы.
Управление вакансиями (Core Subdomain) — в данном контексте, где максимизация продаж ключевая задача, управление вакансиями может играть важную роль в найме квалифицированных сотрудников для обеспечения эффективной работы магазинов.
![](https://habrastorage.org/getpro/habr/upload_files/c2f/8de/fe4/c2f8defe4102ececead174051caff78f.png)
В первом примере фокус был на управлении компетенциями, а во втором примере мы рассмотрели контекст максимизации продаж с другими акцентами на одних и те же поддоменах. Как вы смогли убедиться, для разных систем одни и те же поддомены могут иметь разную значимость.
Внутри контекста желательно определять как вы взаимодействуйте с теми или иными поддоменами, чтобы лучше понимать логику внутренних взаимосвязей, иметь возможность определять приоритеты и принимать решения по дизайну архитектуры внутри контекста.
Например, Core subdomain является ключевым, поэтому хорошо бы по особенному отнестись к его проектированию. В то время как Generic и Supporting можно закрыть вообще аутсорсом/покупкой готового решения. Используйте знания о типах поддоменов, они могут дать много информации, которая поможет в управлении командой.
Поддомены часто выделяются с учетом потребностей и задач, решаемых в рамках конкретного контекста. Они могут быть уникальными для определенных ограниченных контекстов, и это часто зависит от того, какие аспекты предметной области важны и активно используются внутри этого контекста. Не обязательно придерживаться точного копирования структуры компании или существующих департаментов. Вместо этого, вы должны анализировать бизнес-процессы, взаимодействие и уникальные требования в каждой области вашего приложения. Определение поддоменов должно быть направлено на обеспечение лучшего соответствия потребностям и логике конкретных бизнес-задач в рамках каждого ограниченного контекста.
Заключение
Установка границ в Bounded Contexts, напоминающая принцип единственной ответственности, вносит ясность в организацию и структуру системы. Этот принцип гласит: "Что принадлежит одной области, должно оставаться в этой же области". После определения логических границ, наша работа становится более управляемой, поскольку области ответственности четко очерчены. Разделение достигается путем сокрытия сложных бизнес-правил и предоставления узкого и стойкого интерфейса.
Для успешного разделения систем и бизнес процессов вам необходимо искать конфликты в ваших моделях. Разделяйте ответственность, ищите поддомены. Сделайте так, чтобы каждая команда и система работала в своем изолированном контексте. Безусловно, можно поддерживать несколько контекстов, но ни в коем случае не старайтесь эти контексты смешивать. Внутри контекста желательно определять как вы взаимодействуйте с теми или иными поддоменами, чтобы иметь возможность определять приоритеты и принимать решения по дизайну архитектуры.
В результате предоставленных рекомендаций получается набор ценных стратегий для эффективного управления сложностью системы. Эти рекомендации направлены на создание более модульной, гибкой и легко поддерживаемой системы.