Разбираем Bounded Context на примере онлайн-школы. Где границы в твоём проекте?
Представь, что мы создаем систему, которая автоматизирует работу с клиентами в онлайн-школе английского языка. Онлайн-школа запускает курсы для детей. Учится ребёнок, платит родитель, а в системе это один класс Client. Совершенно непонятно, кто клиент: ребёнок или родитель? Хорошо, ты добавляешь в класс пару полей и флагов, чтобы отличать их друг от друга. Но дальше — больше: чтобы бухгалтерия выставила счёт, а учебный отдел записал ученика в группу, также нужно поставить пару флагов. Через месяц приходит маркетинг и просит добавить свои правила, и класс Client становится всё больше и противоречивее, каждая новая правка делает систему сложнее.
Знакомая ситуация? Проблему решит Bounded Context. Разберём на примере, в чём корневая проблема, почему рефакторинг не спасает и как решить проблему. А в конце статьи я дам признаки, по которым нужно проводить границы Bounded Context.
Один «клиент», четыре разных смысла
Хоть клиент и один, но для разных предметных областей он несет совершенно разный смысл и обладает совершенно разным поведением. Для маркетинга клиент — это лид с источником заявки, для продаж — сделка с менеджером и этапом. Учебный отдел видит ученика с уровнем и группой, бухгалтерия видит плательщика с договором. А на детских курсах ученик и плательщик вообще разные люди.
В DDD понятия бизнеса код описывает через доменную модель: набор классов с атрибутами и правилами. Если мы все четыре смысла вложим в одну доменную модель, то она будет выглядеть так:
Один общий Client смешивает данные и правила четырёх предметных областей
Представь класс Product, который используется сразу для задач 4 разных отделов компании. Каждый из них меняет его по своим причинам, из-за чего нарушается принцип единственной ответственности.
Такой разросшийся класс называют God Object — объектом, который берёт на себя слишком много слабо связанных обязанностей. Его сложно поддерживать, изменять и развивать, поскольку изменения для одного контекста могут затронуть другие.
Если ты встречал класс с десятками полей и методов, который занимает несколько экранов, — возможно, перед тобой именно God Object.
Client - типичный God Object:
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 больше нет.
Каждый контекст использует собственную доменную модель и меняет её независимо от остальных.
У каждого Bounded Context своя доменная модель
Если вынести каждую модель в отдельный микросервис, получатся хорошо разделённые и слабо связанные сервисы.
С точки зрения организации разработки тоже есть плюсы: удобно закреплять за контекстом одну команду, которая отвечает за него целиком, от интерфейса до хранения данных. Это делает команды менее связанными и снижает когнитивную нагрузку.
Каждая команда отвечает за собственный контекст
Каждая команда отвечает за свой контекст и может развивать его независимо от остальных команд.
Как найти границы Bounded Context
Чтобы ответить на эти вопросы, нужно разбираться в бизнес-процессах, обсуждать их с заказчиком и искать границы контекстов. На практике это не так просто.
Но есть удобный инструмент — EventStorming. Он позволяет бизнесу и разработке совместно и достаточно быстро разобрать процессы, выделить сущности, их поведение и определить границы Bounded Context.
Участники вместе выстраивают цепочку бизнес-событий. При этом разные названия одного и того же события могут стать первым сигналом, что мы имеем дело с несколькими контекстами.
Итог
А нет ли в твоём проекте God object? Возможно, единую доменную модель уже пора разделить на несколько. Открой самый большой класс в проекте и посмотри, какие домены (бизнес-заказчики) влияют на его изменения и что каждый из них понимает под его названием. Если за одним названием скрываются разные смыслы — возможно, ты нашёл первую границу Bounded Context.