Что такое Bounded Context и какая от него польза

Разбираем Bounded Context на примере онлайн-школы. Где границы в твоём проекте?
Представь, что мы создаем систему, которая автоматизирует работу с клиентами в онлайн-школе английского языка. Онлайн-школа запускает курсы для детей. Учится ребёнок, платит родитель, а в системе это один класс Client. Совершенно непонятно, кто клиент: ребёнок или родитель? Хорошо, ты добавляешь в класс пару полей и флагов, чтобы отличать их друг от друга. Но дальше — больше: чтобы бухгалтерия выставила счёт, а учебный отдел записал ученика в группу, также нужно поставить пару флагов. Через месяц приходит маркетинг и просит добавить свои правила, и класс Client становится всё больше и противоречивее, каждая новая правка делает систему сложнее.
Знакомая ситуация? Проблему решит Bounded Context. Разберём на примере, в чём корневая проблема, почему рефакторинг не спасает и как решить проблему. А в конце статьи я дам признаки, по которым нужно проводить границы Bounded Context.

Один «клиент», четыре разных смысла

Хоть клиент и один, но для разных предметных областей он несет совершенно разный смысл и обладает совершенно разным поведением. Для маркетинга клиент — это лид с источником заявки, для продаж — сделка с менеджером и этапом. Учебный отдел видит ученика с уровнем и группой, бухгалтерия видит плательщика с договором. А на детских курсах ученик и плательщик вообще разные люди.
В DDD понятия бизнеса код описывает через доменную модель: набор классов с атрибутами и правилами. Если мы все четыре смысла вложим в одну доменную модель, то она будет выглядеть так:
Маркетинг, продажи, учебный отдел и бухгалтерия зависят от общего класса Client с разными смыслами
Один общий Client смешивает данные и правила четырёх предметных областей
Представь класс Product, который используется сразу для задач 4 разных отделов компании. Каждый из них меняет его по своим причинам, из-за чего нарушается принцип единственной ответственности.
Такой разросшийся класс называют God Object — объектом, который берёт на себя слишком много слабо связанных обязанностей. Его сложно поддерживать, изменять и развивать, поскольку изменения для одного контекста могут затронуть другие.
Если ты встречал класс с десятками полей и методов, который занимает несколько экранов, — возможно, перед тобой именно God Object.
Client - типичный God Object:
Класс Client содержит данные и методы маркетинга, продаж, обучения и бухгалтерии
Client как God Object: четыре контекста меняют один класс по разным причинам

Почему рефакторинг не поможет

Решение в лоб — продолжать дописывать единый класс Client при поступлении новых требований. Поля можно переименовать, флаги вынести в отдельные классы, но смыслы, логика, лишние поля всё равно будут конфликтовать внутри одной модели.
И чем больше такой класс, тем сильнее растёт цена его поддержки и развития. Быстрый старт, потом разработка замедляется. На общий класс завязано всё больше команд, каждая правка требует согласований, и класс начинают бояться трогать. Обсуждения с бизнесом превращаются в спор, чей «клиент» правильный.
Рефакторинг лишь структурирует код, но без разделения моделей проблема никуда не уйдёт, нужно действовать более кардинально.

Bounded Context что это

Решение состоит в том, чтобы признать: единой доменной модели для всей системы не существует, и её нужно разделить на несколько отдельных.
Bounded Context (ограниченный контекст) — это явная граница, внутри которой у каждого термина одно значение и одна модель.
По сути мы должны получить несколько доменных моделей с высокой семантической согласованностью.
Для школы получается четыре Bounded Context:
КонтекстМодельКто клиент для отделаЧто важно модели
ПривлечениеLeadлидисточник заявки
ПродажиDealсделкаменеджер, этап
ОбучениеStudentученикуровень, группа
БиллингPayerплательщикдоговор
Student здесь ученик, Payer — плательщик со своим поведением и атрибутами: счёт, дата оплаты и так далее. Нам больше не нужно пытаться впихнуть 2 смысла в 1 класс Client. Конфликта и распухшей единой сущности больше нет.
По сути Lead, Student, Payer - это всё ещё тот же клиент школы, но выраженные через отдельные классы и нейминг.
Разделение одной доменной модели на отдельные и называется делением по Bounded Context. А сам Bounded Context — это по сути границы отдельной доменной модели.

Какие выгоды дает Bounded Context?

Маленькие согласованные модели проще поддерживать. Бухгалтерия поменяла правила договора, и ты правишь только Payer в Биллинге, а Student и группы об этом даже не узнают. Изменения остаются локальными, распухшего Client больше нет.
Каждый контекст использует собственную доменную модель и меняет её независимо от остальных.
Четыре отдельные доменные модели: Lead, Deal, Student и Payer
У каждого Bounded Context своя доменная модель
Если вынести каждую модель в отдельный микросервис, получатся хорошо разделённые и слабо связанные сервисы.
С точки зрения организации разработки тоже есть плюсы: удобно закреплять за контекстом одну команду, которая отвечает за него целиком, от интерфейса до хранения данных. Это делает команды менее связанными и снижает когнитивную нагрузку.
Четыре команды отвечают каждая за свой Bounded Context и доменную модель
Каждая команда отвечает за собственный контекст
Каждая команда отвечает за свой контекст и может развивать его независимо от остальных команд.

Как найти границы Bounded Context

Чтобы ответить на эти вопросы, нужно разбираться в бизнес-процессах, обсуждать их с заказчиком и искать границы контекстов. На практике это не так просто.
Но есть удобный инструмент — EventStorming. Он позволяет бизнесу и разработке совместно и достаточно быстро разобрать процессы, выделить сущности, их поведение и определить границы Bounded Context.
Участники вместе выстраивают цепочку бизнес-событий. При этом разные названия одного и того же события могут стать первым сигналом, что мы имеем дело с несколькими контекстами.

Итог

А нет ли в твоём проекте God object? Возможно, единую доменную модель уже пора разделить на несколько. Открой самый большой класс в проекте и посмотри, какие домены (бизнес-заказчики) влияют на его изменения и что каждый из них понимает под его названием. Если за одним названием скрываются разные смыслы — возможно, ты нашёл первую границу Bounded Context.