Ограниченные контексты
Почему одно слово «курс» требует разных моделей у маркетинга, учебной части и бухгалтерии, что такое ограниченный контекст, как искать его границы по языку, командам, процессам и данным, как контекст связан с командой (закон Конвея) и с сервисом — и как описать контекст канвой DDD Crew. Практика: разбиваем онлайн-школу на контексты.
Зачем это нужно
Заголовок раздела «Зачем это нужно»Онлайн-школа решила навести порядок и завести «единую модель курса» для всех отделов. Маркетингу нужны текст для
витрины, обложка и акционная цена. Учебной части — программа занятий и проходной балл. Менеджерам набора — дата
старта и число мест. Бухгалтерии — сумма в счёте, ставка налога и шаблон договора. Через полгода в классе Course
сорок полей, и каждое изменение требует согласия четырёх отделов. Маркетинг запускает акцию «−20 %», и в уже
выставленных счетах тихо меняется сумма. Учебная часть добавляет курсу второй поток, и непонятно, какая из двух дат
старта — «дата старта курса».
Никто не ошибся в мелочах. Ошибка в замысле: слово «курс» в разных частях школы называет разные вещи, а их попытались уместить в одну модель. В уроке DDD.1.2 мы заметили, что единый язык однозначен только внутри своей области, а в уроке DDD.1.3 разделили школу на поддомены. Теперь проведём границы моделей в самой программе. Эти границы называются ограниченными контекстами, и это, пожалуй, главное понятие стратегического проектирования.
Одно слово — разные модели
Заголовок раздела «Одно слово — разные модели»Посмотрим, что значит «курс» для разных людей в школе:
| Кто говорит | Что для него «курс» | Что ему важно | Что ему не нужно |
|---|---|---|---|
| Маркетолог (Каталог) | товар на витрине | название, описание, обложка, цена, акции | даты занятий, проходной балл |
| Менеджер набора (Набор) | чаще всего конкретный поток: «на курс 2 ноября осталось 3 места» | дата старта, вместимость, записи | текст лендинга |
| Методист (Обучение) | программа занятий и заданий | занятия, задания, сроки сдачи, прогресс | цена и скидки |
| Бухгалтер (Оплата) | строка в счёте | сумма, плательщик, дата платежа | программа занятий |
| Сотрудник Сертификации | то, что напечатают в сертификате | название, объём в часах, условия выдачи | вместимость потока |
flowchart LR
W(("«курс»"))
W --- K["Каталог<br/>товар на витрине:<br/>описание, цена"]
W --- N["Набор<br/>поток: старт,<br/>вместимость, записи"]
W --- L["Обучение<br/>программа: занятия,<br/>задания, прогресс"]
W --- P["Оплата<br/>строка в счёте:<br/>сумма, плательщик"]
W --- C["Сертификация<br/>название и часы<br/>в сертификате"]Можно ли всё же построить одну модель на всех? Эванс отвечает в справочнике шаблонов прямо: на любом крупном проекте неизбежно появляется несколько моделей, и полная унификация модели большой системы не будет ни осуществимой, ни выгодной. Авторы «Cosmic Python» приводят тот же пример со словом «клиент»: у продаж, поддержки и логистики это разные понятия с разными атрибутами. Хуже того, одинаковое имя может скрывать совершенно разный смысл.
Цена единой модели знакома по уроку DDD.1.1:
- класс-свалка — у
Courseдесятки полей, из которых каждому отделу нужны три-четыре; - размытые слова — чтобы «курс» подошёл всем, определение делают таким общим, что оно перестаёт что-либо определять;
- все зависят от всех — изменение ради маркетинга ломает бухгалтерию; каждое решение требует собрания;
- неверные правила — правило одного отдела применяется в чужом смысле, как с акцией и выставленными счетами.
Выход — разрешить нескольким моделям сосуществовать, но каждой провести явную границу.
Что такое ограниченный контекст
Заголовок раздела «Что такое ограниченный контекст»Ограниченный контекстограниченный контекстEN bounded contextЯвная граница, внутри которой одна модель предметной области и её единый язык однозначны и согласованы. За границей те же слова могут значить другое. Границу закрепляют командой, частью приложения, кодом и данными; перевод между контекстами делают явно. (bounded context) — явная граница, внутри которой одна модель предметной области и её единый язык однозначны и согласованы. Внутри контекста «курс» значит ровно одно. Снаружи то же слово может значить другое — и это нормально, если граница видна и переход через неё сделан явно.
Эванс формулирует шаблон примерно так (пересказ): явно определите контекст, в котором действует модель. Задайте его границы через организацию команд, через то, в каких частях приложения модель используется, и через физическое воплощение — кодовые базы и схемы баз данных. Внутри границ держите модель строго согласованной, а проблемами снаружи не отвлекайтесь.
Из определения следует, что у контекста есть всё своё:
| Что | Пример для Набора |
|---|---|
| Язык | поток, запись на поток, место, лист ожидания, «ожидает оплаты» |
| Модель | Cohort, Enrollment, Waitlist, правила записи и отмены |
| Код | пакет school/enrollment/ с собственными тестами |
| Данные | свои таблицы; чужие таблицы Набор не читает и не пишет |
| Команда | одна команда-владелец, к ней идут с вопросами о правилах Набора |
| Вход и выход | что контекст принимает (команды, запросы, события) и что публикует |
Самая важная из этих границ — языковая. Вот как одного и того же человека называют в разных контекстах школы:
| Контекст | Как называют человека | Что о нём знают |
|---|---|---|
| Набор | студент (Student) | на какие потоки записан, в каком статусе записи |
| Оплата | плательщик (Payer) | реквизиты, счета, платежи; плательщиком может быть компания, которая оплачивает обучение сотрудника |
| Обучение | студент (Student) | сданные задания, прогресс, оценки |
| Уведомления | получатель (Recipient) | адрес, телефон, согласие на рассылки |
Слово «студент» в Наборе и в Обучении совпадает, но модели всё равно разные: Набору не нужны оценки, Обучению — статус
счёта. Связывает их только идентификатор студента. Общий класс Student на все контексты — ровно та ошибка, с
которой начался урок, только со студентом вместо курса.
Поддомен и контекст: задача и решение
Заголовок раздела «Поддомен и контекст: задача и решение»В уроке DDD.1.3 мы договорились: поддомены описывают пространство задачи (как устроен бизнес), а контексты — пространство решения (как устроена программа). Влад Хононов формулирует различие коротко: поддомены обнаруживают, контексты проектируют.
| Поддомен | Ограниченный контекст | |
|---|---|---|
| Что это | часть бизнеса со своими задачами и экспертами | граница модели и языка в программе |
| Откуда берётся | существует, даже если программы нет | его проводит команда проектирования |
| Пример | набор студентов на потоки | пакет school/enrollment/ со своей моделью, данными и командой |
| Вопрос | чем занимается бизнес? | где кончается действие этой модели? |
В учебной школе мы для простоты сделали соответствие один к одному: шесть поддоменов — шесть контекстов. Вон Вернон советует стремиться именно к этому, и для нового проекта это хорошая отправная точка. Но в жизни соответствие часто другое:
- один контекст шире поддомена. Школа купила готовую учебную платформу, и в ней живут и занятия, и сертификаты, и часть оплаты. Это один контекст (одна чужая модель) на три поддомена. Так же выглядит большая старая система, в которую годами добавляли всё подряд;
- один поддомен разрезан на несколько контекстов. Обучение выросло: появились автоматическая проверка кода, ревью наставников и видеоуроки. Модель «задания и его проверки» у автоматической проверки и у наставника начала расходиться, команды разделились — и поддомен Обучение стал двумя контекстами: «Занятия и прогресс» и «Проверка заданий»;
- контекст без поддомена в бизнес-смысле. Например, контекст-«переводчик» на границе с внешней системой, который только принимает её сообщения и переводит на язык школы. О таком контексте поговорим в уроке DDD.2.2.
Поэтому поддомены и контексты держат в голове раздельно: первые говорят, что важно, вторые — где проходят швы программы.
Как искать границы
Заголовок раздела «Как искать границы»Алгоритма нет, но есть четыре надёжных признака. Хорошо, когда несколько из них указывают на одну и ту же линию.
Граница там, где слово меняет смысл или где появляются свои слова. Слушайте, как говорят люди, и отмечайте переходы: «курс» превращается в «поток», «студент» — в «плательщика», «оплата» распадается на «счёт» и «платёж». Если внутри предполагаемого контекста одно слово приходится уточнять каждый раз («какой именно курс?»), это два контекста, а не один.
Команды и эксперты
Заголовок раздела «Команды и эксперты»Спросите: кто решает, как здесь должно быть? Правила записи определяет менеджер набора, правила проверки заданий — методист, правила возвратов вместе решают бухгалтерия и юрист. Разные эксперты — сильный признак разных контекстов. Второй вопрос — какая команда разработчиков будет это сопровождать. О нём подробнее в разделе о законе Конвея.
Процессы
Заголовок раздела «Процессы»Путь студента состоит из этапов: выбрал курс → записался → оплатил → учится → получил сертификат. Между этапами обычно стоит событие, после которого «начинается другая жизнь»: ЗаписьПодтверждена передаёт студента из Набора в Обучение, СертификатВыдан завершает историю. Такие переходы — естественные места для границ. Как найти их на стене стикеров вместе с экспертами, разберём в уроке DDD.2.3 об Event Storming.
Что меняется вместе и должно быть согласовано мгновенно? Число записей и вместимость потока должны быть согласованы всегда: иначе на поток запишется лишний студент. Значит, они в одном контексте. А сертификат может появиться через минуту после завершения курса — эти данные можно согласовывать «в конечном счёте» через события, и их можно разнести. Второй вопрос о данных — кто ими владеет: изменять запись на поток может только Набор, остальные узнают об изменениях из событий или спрашивают через открытый интерфейс.
Слишком крупно или слишком мелко
Заголовок раздела «Слишком крупно или слишком мелко»| Признак | Что не так | Что делать |
|---|---|---|
| в одном контексте одно слово в двух смыслах, две группы экспертов не понимают друг друга | контекст слишком крупный | разрезать по языку |
| две части почти каждое изменение делают вместе, постоянно вызывают друг друга | граница проведена не там или контексты слишком мелкие | объединить или передвинуть границу |
| контекст назван существительным: «Студенты», «Курсы» | нарезка по сущностям, а не по возможностям бизнеса: у каждой части школы свой «студент» | резать по тому, что бизнес делает: набор, обучение, оплата |
| в контексте нет ни одного правила, только чтение чужих данных | возможно, это не контекст, а отчёт или экран | оставить частью другого контекста или моделью чтения |
Особенно частая ошибка — третья строка. Контекст «Студенты» с полным классом студента быстро становится тем самым классом-свалкой: туда все приходят за своими полями. Режьте по глаголам бизнеса, а не по существительным.
Контекст и команда: закон Конвея
Заголовок раздела «Контекст и команда: закон Конвея»В 1968 году Мелвин Конвей заметил закономерность, которую теперь называют законом Конвеязакон КонвеяEN Conway's lawНаблюдение Мелвина Конвея (1968): организация проектирует системы, повторяющие структуру коммуникаций между её людьми и командами. Отсюда совет совмещать границы контекстов с границами команд. «Обратный манёвр Конвея» — сначала устроить команды так, как должна выглядеть система.: организация, которая проектирует систему, создаёт систему, повторяющую структуру коммуникаций в самой организации. Популярная шутливая формулировка: если компилятор пишут четыре группы, получится четырёхпроходный компилятор. Если отделы не разговаривают друг с другом, их части системы тоже будут плохо стыковаться.
Для DDD из этого два практических вывода:
- У контекста один владелец. Если модель Набора правят три команды, языковая граница размоется: каждая будет тянуть слова в свою сторону. Одна команда может владеть несколькими контекстами, но контекст не стоит делить между командами.
- Границы команд и контекстов лучше совмещать. Тогда то, что должно часто обсуждаться, обсуждается внутри команды, а между командами остаются редкие и явные договорённости. Такой ход иногда называют «обратным манёвром Конвея»: сначала устроить команды так, как должна выглядеть система, и система сама потянется за ними.
Процесс DDD Crew посвящает этому шаг Organise: команды должны быть автономными и выстроенными по границам контекстов, а размер зоны ответственности выбирают с учётом доступных людей, когнитивной нагрузки, затрат на общение и того, сколько людей знает каждую часть. В онлайн-школе с шестью разработчиками разумно так:
flowchart LR subgraph T1["Команда «Набор и деньги» (3 чел.)"] N["Набор<br/>core"] P["Оплата<br/>generic"] end subgraph T2["Команда «Учёба» (2 чел.)"] L["Обучение<br/>core"] C["Сертификация<br/>supporting"] end subgraph T3["Команда «Витрина» (1 чел.)"] K["Каталог<br/>supporting"] U["Уведомления<br/>generic"] end K --> N N <--> P N --> L L --> C N -.-> U C -.-> U
Почему так? Набор и Оплата связаны теснее всего — счёт, платёж, подтверждение и возвраты, поэтому их ведёт одна команда. Оба основных контекста получили своих людей, а один человек с двумя простыми контекстами справится, потому что Каталог — CRUD, а Уведомления — адаптер к купленному сервису. Если завтра школа наймёт ещё пятерых, первым кандидатом на отдельную команду станет Обучение: оно core, и в нём больше всего работы.
Контекст и сервис
Заголовок раздела «Контекст и сервис»Контекст — это логическая граница модели, а сервис (отдельно запускаемое приложение) — единица развёртывания. Они связаны, но не обязаны совпадать.
| Вариант | Как выглядит в школе | Когда уместен |
|---|---|---|
| несколько контекстов в одном приложении (модульный монолит) | одно приложение, внутри пакеты catalog, enrollment, payment… со своими моделями и таблицами | начало проекта, маленькая команда, границы ещё уточняются |
| один контекст — один сервис | Набор — отдельный сервис со своей базой | границы устоялись, контексту нужны свои релизы или своё масштабирование |
| один контекст — несколько процессов | Уведомления: веб-приложение для настроек и отдельный обработчик очереди писем | разные требования к нагрузке у частей одной модели |
| несколько контекстов делят таблицы и классы | все читают и пишут таблицу courses | никогда: это ком грязи, даже если он разнесён по сервисам |
Последняя строка важна. Пять микросервисов, которые ходят в одну общую базу, — это не пять контекстов, а один «распределённый ком грязи»: развёртывать стало сложнее, а модели по-прежнему общие. И наоборот, аккуратный модульный монолитмодульный монолитEN modular monolithОдно развёртываемое приложение, разделённое на модули по ограниченным контекстам: у модуля публичный интерфейс и свои данные, чужие внутренности не импортируют. Границы дёшево передвигать; модуль можно позже вынести в отдельный сервис., в котором контексты разделены по пакетам и не лезут во внутренности друг друга, — честное воплощение ограниченных контекстов. Для учебной школы начнём именно с него:
school/ catalog/ # Каталог: карточки курсов, публикация на витрине enrollment/ # Набор: Cohort, Enrollment, Waitlist, правила записи payment/ # Оплата: Invoice, Payment, перевод ответов платёжной системы learning/ # Обучение: занятия, задания, прогресс certification/ # Сертификация: выдача сертификатов notifications/ # Уведомления: шаблоны писем, подписка на событияПравило для такого проекта простое: контекст обращается к другому только через его открытый интерфейс (функции
модуля api.py или события), а не через чужие классы и таблицы. Когда контексты стоит выносить в отдельные сервисы,
разберём в уроке DDD.4.2.
Канва ограниченного контекста
Заголовок раздела «Канва ограниченного контекста»Чтобы граница не осталась линией на доске, её описывают. Удобный шаблон — канва ограниченного контекстаканва ограниченного контекстаEN Bounded Context CanvasШаблон DDD Crew (CC BY 4.0) для проектирования и описания одного контекста на одном листе: название, назначение, стратегическая классификация, роли, входящие и исходящие сообщения, язык, бизнес-решения, допущения, метрики проверки и открытые вопросы. (Bounded Context Canvas) от сообщества DDD Crew (лицензия CC BY 4.0). Это один лист с разделами; авторы советуют начинать с названия и назначения, а остальное заполнять в любом порядке — снаружи внутрь (от входящих сообщений) или изнутри наружу (от правил и языка).
| Раздел канвы | Вопрос, на который он отвечает |
|---|---|
| Name — название | как мы называем контекст; согласовано ли название всей командой |
| Purpose — назначение | зачем контекст нужен бизнесу, на языке бизнеса, без технических деталей |
| Strategic Classification — классификация | тип поддомена (core, supporting, generic); роль в бизнес-модели (приносит доход, вовлекает пользователей, защищает от рисков и требований закона); стадия эволюции по Уордли |
| Domain Roles — роли | как контекст себя ведёт: выполняет процесс, анализирует, одобряет, переводит между языками, стоит шлюзом на краю системы |
| Inbound Communication — входящие сообщения | кто к нам обращается и с чем: команды («сделай»), запросы («скажи»), события («случилось») |
| Outbound Communication — исходящие сообщения | к кому обращаемся мы и с чем |
| Ubiquitous Language — язык | ключевые термины контекста и их значения |
| Business Decisions — бизнес-решения | главные правила и политики |
| Assumptions — допущения | на каких предположениях держится проект границ |
| Verification Metrics — метрики проверки | как поймём, что граница выбрана удачно |
| Open Questions — открытые вопросы | что никто в комнате пока не знает |
Обратите внимание на три вида сообщений: команда, запрос и событие. Авторы канвы подчёркивают, что «сообщение» здесь
понимается широко и не требует ни очереди, ни шины: отправка HTML-формы — тоже команда. Вот канва Набора, записанная
текстом, — так её удобно хранить в репозитории рядом с кодом (docs/contexts/enrollment.md):
# Канва контекста: Набор (enrollment)
**Назначение.** Заполнять потоки студентами честно и полностью: записывать на потоки сограниченным числом мест, держать место до оплаты, вести лист ожидания, отмены и возвраты.
**Классификация.** Основной поддомен (core). Роль в бизнес-модели: приносит доход.Эволюция: сделано на заказ — готового решения с нашими правилами нет.
**Роли.** Выполняет процесс записи (execution); одобряет или отклоняет запись по правилам.
**Входящие сообщения.**- от студента через сайт: команды «записаться на поток», «отменить запись», «встать в лист ожидания»;- от Каталога: событие «поток открыт для записи» (курс, дата старта, вместимость, цена);- от Оплаты: событие ПлатёжПолучен (номер счёта, сумма);- от планировщика: команда «отменить неоплаченные записи старше 48 часов».
**Исходящие сообщения.**- в Оплату: команда «выставить счёт»; при отмене за 7 суток и более — команда «вернуть деньги»;- всем заинтересованным: события СтудентЗаписан, ЗаписьПодтверждена, ЗаписьОтменена, МестоОсвободилось.
**Язык.** Поток, вместимость, место, запись на поток, статусы «ожидает оплаты», «подтверждена»,«отменена», лист ожидания. Полный список — в docs/glossary.md, раздел «Набор».
**Бизнес-решения.** Шесть правил Набора: не больше вместимости; не дважды на один поток; записьзакрывается при старте; 48 часов на оплату; возврат при отмене за 7 суток и более; место из листаожидания — первому, 24 часа на подтверждение. Числа — условия учебного примера.
**Допущения.** Цена фиксируется в счёте в момент записи. Набору не нужно знать, как студент платит.
**Метрики проверки.** Сколько задач требуют одновременных изменений в Наборе и другом контексте;сколько вопросов о правилах Набора уходит не к его команде.
**Открытые вопросы.** Можно ли снова записаться после отмены? Что делать с платежом, пришедшимпосле автоматической отмены? Что подтверждает студент из листа ожидания?Открытые вопросы перекочевали сюда из глоссария урока DDD.1.2, и это нормально: канва — живой документ. Мы закроем их в уроке DDD.2.3, где разберём процесс записи вместе с экспертами.
Метрики проверки заслуживают отдельного слова. Граница — гипотеза, и её проверяют практикой. Если половина задач Набора требует правок в Оплате, граница проведена неудачно или контексты слишком тесно связаны. Если вопросы о правилах Набора приходят к команде «Витрина», язык или ответственность размыты.
Канва полезна и для критики уже нарисованного. В README DDD Crew есть советы по проверке интерфейса контекста — того, что видят соседи:
- согласуются ли названия сообщений друг с другом и с назначением контекста?
- правильно ли выбран вид каждого сообщения — не должна ли команда быть событием?
- не слишком ли велик интерфейс — сколько в нём разных видов сообщений?
- не выставляет ли контекст наружу свои внутренности?
- нет ли сообщений, которым место в другом контексте?
И общий совет: попробуйте мысленно перенести что-то на канве в соседний контекст — например, лист ожидания в Уведомления — и посмотрите, что сломается. Если ничего, возможно, граница не там.
Практика
Заголовок раздела «Практика»Одна модель против моделей по контекстам
Заголовок раздела «Одна модель против моделей по контекстам»Вернёмся к истории из начала урока и воспроизведём баг. Вот «курс для всех» и счёт, который на него ссылается:
from dataclasses import dataclassfrom datetime import datetime
@dataclassclass Course: """«Курс для всех»: витрина, набор, учёба и бухгалтерия в одном классе.""" title: str price_kopecks: int # маркетинг меняет цену ради акций starts_at: datetime # набору нужна дата старта… но у курса бывает несколько потоков capacity: int lessons: list # учебной части нужны занятия
@dataclassclass Invoice: """Счёт, который ссылается на общий курс вместо того, чтобы хранить свою сумму.""" payer: str course: Course
def amount_kopecks(self): return self.course.price_kopecks # сумма берётся «живой» из курса
python = Course("Python для аналитиков", 3_000_000, datetime(2026, 11, 2, 10, 0), 30, ["Введение", "pandas"])anna_invoice = Invoice("anna", python)print(f"Счёт Анны выставлен на {anna_invoice.amount_kopecks() / 100:.2f} руб.")
python.price_kopecks = 2_400_000 # маркетинг запустил акцию: −20 % для новых студентовprint(f"После акции в том же счёте: {anna_invoice.amount_kopecks() / 100:.2f} руб.")Счёт Анны выставлен на 30000.00 руб.После акции в том же счёте: 24000.00 руб.Анна получила счёт на 30 000 руб., а через час тот же счёт показывает 24 000. Бухгалтерия видит расхождение с уже отправленным документом, поддержка не может объяснить, сколько Анна должна. Код при этом «правильный»: в общей модели цена курса и сумма счёта — одно поле, хотя для маркетинга и для бухгалтерии это разные понятия.
Теперь разделим модели по контекстам. В проекте это три пакета; здесь для запуска — один файл, а контекст подписан комментарием:
from dataclasses import dataclass, replacefrom datetime import datetime
# --- Каталог (school/catalog): курс как товар на витрине ---@dataclass(frozen=True)class CourseCard: course_id: str title: str price_kopecks: int
# --- Набор (school/enrollment): конкретный запуск курса ---@dataclass(frozen=True)class Cohort: cohort_id: str course_id: str # связь с Каталогом — только идентификатор starts_at: datetime capacity: int
# --- Оплата (school/payment): требование оплатить, сумма зафиксирована ---@dataclass(frozen=True)class Invoice: invoice_id: str payer: str # в Оплате говорят «плательщик», а не «студент» description: str amount_kopecks: int
def issue_invoice(invoice_id, student_id, card, cohort): """Выставить счёт: Оплата копирует из чужих моделей только то, что нужно ей, — в момент выставления.""" return Invoice( invoice_id=invoice_id, payer=student_id, description=f"{card.title}, поток {cohort.starts_at:%d.%m.%Y}", amount_kopecks=card.price_kopecks, )
card = CourseCard("python-analytics", "Python для аналитиков", 3_000_000)cohort = Cohort("python-2026-11", "python-analytics", datetime(2026, 11, 2, 10, 0), capacity=30)invoice = issue_invoice("INV-2026-0042", "anna", card, cohort)print(f"{invoice.invoice_id}: «{invoice.description}» — {invoice.amount_kopecks / 100:.2f} руб.")
card = replace(card, price_kopecks=2_400_000) # акция меняет только карточку в Каталогеprint(f"Цена на витрине: {card.price_kopecks / 100:.2f} руб.")print(f"Счёт Анны: {invoice.amount_kopecks / 100:.2f} руб. ← выставленный счёт не изменился")INV-2026-0042: «Python для аналитиков, поток 02.11.2026» — 30000.00 руб.Цена на витрине: 24000.00 руб.Счёт Анны: 30000.00 руб. ← выставленный счёт не изменилсяЧто изменилось:
- у каждого контекста своя маленькая модель с полями, нужными ему. Это правило из «Cosmic Python»: в модели только данные, нужные для её расчётов;
- контексты ссылаются друг на друга только по идентификаторам (
course_id), а не держат чужие объекты; - на границе происходит явный перевод:
issue_invoiceберёт из Каталога и Набора ровно то, что нужно Оплате, и превращает в понятия Оплаты — плательщик, описание, сумма. После этого счёт живёт своей жизнью; - слово «курс» больше не спорит само с собой: в Каталоге —
CourseCard, в Наборе —Cohort, в Оплате — строкаdescriptionв счёте.
frozen=True делает объекты неизменяемыми (подробно — в уроке DDD.3.1), поэтому акция создаёт новую карточку через
replace, а не правит старую. Но главное здесь не frozen: даже с изменяемыми классами счёт уже не зависит от
витрины, потому что сумма в нём — своя.
Задание: разбить онлайн-школу на контексты
Заголовок раздела «Задание: разбить онлайн-школу на контексты»Ниже — фрагменты требований и понятия, которые прозвучали на встречах с экспертами. Для каждого решите, в каком контексте он живёт: Каталог, Набор, Оплата, Обучение, Сертификация, Уведомления. Если фрагмент касается двух контекстов, решите, кто владелец, а кто только узнаёт о факте.
- «Обложка и описание курса для лендинга».
- «На поток не больше 30 студентов».
- «Номер чека и ставка налога в платёжном документе».
- «Срок сдачи домашнего задания — неделя после занятия».
- «Если студент не оплатил за 48 часов, запись отменяется».
- «Письмо со ссылкой на оплату через минуту после записи».
- «Сертификат выдаётся, если сдано не меньше 80 % заданий».
- «Акция: −20 % на курс до конца месяца».
- «Прогресс студента по курсу в процентах».
- «Освободившееся место предлагают первому в листе ожидания».
- «Возврат денег при отмене за 7 суток и более до старта».
- «Отписаться от рассылки о новых курсах».
Возможный разбор
1 — Каталог. 2 — Набор (вместимость потока). 3 — Оплата. 4 — Обучение. 5 — Набор: правило о записи; Оплата лишь сообщает, пришёл ли платёж. 6 — Уведомления отправляют письмо, но решение «пора писать» запускает событие СтудентЗаписан из Набора, а ссылку на оплату даёт Оплата. 7 — правило выдачи принадлежит Сертификации, а процент сданных заданий она узнаёт от Обучения. 8 — Каталог: цена на витрине; в выставленный счёт сумма попадает уже переводом. 9 — Обучение. 10 — Набор. 11 — интересный случай: решение «положен ли возврат» — правило Набора (оно зависит от записи и даты старта), а исполнение возврата через платёжную систему — Оплата. 12 — Уведомления.
Если вы спорили сами с собой над пунктами 5, 6, 7 и 11, это хороший знак: именно в таких местах проходят границы, и именно для них нужны договорённости между контекстами — тема следующего урока.
Упражнение: канва для Обучения (30 минут)
Заголовок раздела «Упражнение: канва для Обучения (30 минут)»По образцу канвы Набора заполните канву контекста Обучение: назначение, классификация, роли, входящие и исходящие сообщения, язык, бизнес-решения, допущения, метрики и открытые вопросы. Подсказки: что Обучение получает от Набора (какое событие запускает учёбу студента?), что отдаёт Сертификации, какие слова в нём свои («занятие», «задание», «прогресс»), какие правила знает только методист. Скорее всего, раздел «Открытые вопросы» окажется самым длинным — так и должно быть, когда вы впервые описываете контекст без эксперта.
Проверь себя
Заголовок раздела «Проверь себя»- Одно слово в большой системе значит разное: «курс» — товар на витрине, поток, программа занятий, строка в счёте. Одна модель на всех превращается в класс-свалку с размытыми словами, где каждое изменение ломает чужие правила.
- Ограниченный контекст — явная граница, внутри которой модель и язык однозначны. У контекста свои язык, модель, код, данные, команда-владелец и вход-выход; между контекстами — только идентификаторы и явный перевод.
- Поддомены обнаруживают, контексты проектируют. В учебной школе соответствие один к одному, в жизни контекст бывает шире поддомена (купленная платформа, старая система) или поддомен режут на несколько контекстов.
- Границы ищут по языку, экспертам, процессам и данным; режут по возможностям бизнеса, а не по существительным. По закону Конвея у контекста один владелец, а команды выстраивают по границам контекстов.
- Контекст — не обязательно микросервис: модульный монолит с честными границами лучше пяти сервисов с общей базой. Описывать контекст удобно канвой DDD Crew — от назначения до метрик и открытых вопросов.
Дальше: урок DDD.2.2 — карта контекстов. Контексты не живут поодиночке: Набор зависит от Оплаты, Оплата — от внешней платёжной системы. Разберём шаблоны отношений между контекстами и командами и напишем предохранительный слой, который не пускает модель чужой платёжной системы в код школы.