Перейти к содержимому
Программа DDDDDD.2 Стратегическое проектирование

Ограниченные контексты

Почему одно слово «курс» требует разных моделей у маркетинга, учебной части и бухгалтерии, что такое ограниченный контекст, как искать его границы по языку, командам, процессам и данным, как контекст связан с командой (закон Конвея) и с сервисом — и как описать контекст канвой DDD Crew. Практика: разбиваем онлайн-школу на контексты.

УРОКddd/ddd-2-strategic/01-bounded-contexts.mdx1 ч 50 минУрок 1 из 3
После урока вы сможете
  • Объяснить, что такое ограниченный контекст и почему большой системе нужна не одна модель, а несколько с явными границами
  • Находить границы контекстов по языку, командам и экспертам, процессам и данным и отличать контекст от поддомена
  • Рассуждать о связи контекста с командой (закон Конвея) и с сервисом и выбирать разумное соответствие между ними
  • Описать контекст канвой Bounded Context Canvas и разбить онлайн-школу на контексты с их собственными моделями

Онлайн-школа решила навести порядок и завести «единую модель курса» для всех отделов. Маркетингу нужны текст для витрины, обложка и акционная цена. Учебной части — программа занятий и проходной балл. Менеджерам набора — дата старта и число мест. Бухгалтерии — сумма в счёте, ставка налога и шаблон договора. Через полгода в классе 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 из этого два практических вывода:

  1. У контекста один владелец. Если модель Набора правят три команды, языковая граница размоется: каждая будет тянуть слова в свою сторону. Одна команда может владеть несколькими контекстами, но контекст не стоит делить между командами.
  2. Границы команд и контекстов лучше совмещать. Тогда то, что должно часто обсуждаться, обсуждается внутри команды, а между командами остаются редкие и явные договорённости. Такой ход иногда называют «обратным манёвром Конвея»: сначала устроить команды так, как должна выглядеть система, и система сама потянется за ними.

Процесс 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 dataclass
from datetime import datetime
@dataclass
class Course:
"""«Курс для всех»: витрина, набор, учёба и бухгалтерия в одном классе."""
title: str
price_kopecks: int # маркетинг меняет цену ради акций
starts_at: datetime # набору нужна дата старта… но у курса бывает несколько потоков
capacity: int
lessons: list # учебной части нужны занятия
@dataclass
class 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, replace
from 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: даже с изменяемыми классами счёт уже не зависит от витрины, потому что сумма в нём — своя.

Задание: разбить онлайн-школу на контексты

Заголовок раздела «Задание: разбить онлайн-школу на контексты»

Ниже — фрагменты требований и понятия, которые прозвучали на встречах с экспертами. Для каждого решите, в каком контексте он живёт: Каталог, Набор, Оплата, Обучение, Сертификация, Уведомления. Если фрагмент касается двух контекстов, решите, кто владелец, а кто только узнаёт о факте.

  1. «Обложка и описание курса для лендинга».
  2. «На поток не больше 30 студентов».
  3. «Номер чека и ставка налога в платёжном документе».
  4. «Срок сдачи домашнего задания — неделя после занятия».
  5. «Если студент не оплатил за 48 часов, запись отменяется».
  6. «Письмо со ссылкой на оплату через минуту после записи».
  7. «Сертификат выдаётся, если сдано не меньше 80 % заданий».
  8. «Акция: −20 % на курс до конца месяца».
  9. «Прогресс студента по курсу в процентах».
  10. «Освободившееся место предлагают первому в листе ожидания».
  11. «Возврат денег при отмене за 7 суток и более до старта».
  12. «Отписаться от рассылки о новых курсах».
Возможный разбор

1 — Каталог. 2 — Набор (вместимость потока). 3 — Оплата. 4 — Обучение. 5 — Набор: правило о записи; Оплата лишь сообщает, пришёл ли платёж. 6 — Уведомления отправляют письмо, но решение «пора писать» запускает событие СтудентЗаписан из Набора, а ссылку на оплату даёт Оплата. 7 — правило выдачи принадлежит Сертификации, а процент сданных заданий она узнаёт от Обучения. 8 — Каталог: цена на витрине; в выставленный счёт сумма попадает уже переводом. 9 — Обучение. 10 — Набор. 11 — интересный случай: решение «положен ли возврат» — правило Набора (оно зависит от записи и даты старта), а исполнение возврата через платёжную систему — Оплата. 12 — Уведомления.

Если вы спорили сами с собой над пунктами 5, 6, 7 и 11, это хороший знак: именно в таких местах проходят границы, и именно для них нужны договорённости между контекстами — тема следующего урока.

По образцу канвы Набора заполните канву контекста Обучение: назначение, классификация, роли, входящие и исходящие сообщения, язык, бизнес-решения, допущения, метрики и открытые вопросы. Подсказки: что Обучение получает от Набора (какое событие запускает учёбу студента?), что отдаёт Сертификации, какие слова в нём свои («занятие», «задание», «прогресс»), какие правила знает только методист. Скорее всего, раздел «Открытые вопросы» окажется самым длинным — так и должно быть, когда вы впервые описываете контекст без эксперта.

Проверь себя6 вопросов
1 Что такое ограниченный контекст?
2 Какие признаки говорят, что здесь проходит граница контекстов? Выберите все верные варианты.Можно выбрать несколько ответов
3 Школа купила учебную платформу, в которой есть и занятия, и сертификаты. Как соотносятся поддомены и контексты?
4 Команда «Витрина» и команда «Учёба» по очереди правят модель Набора, а у самого Набора владельца нет. Чем это грозит по закону Конвея?
5 В школе пять микросервисов, и все они читают и пишут общую таблицу courses. Сколько здесь ограниченных контекстов на деле?
6 Почему в практике после акции счёт Анны не изменился?
  • Одно слово в большой системе значит разное: «курс» — товар на витрине, поток, программа занятий, строка в счёте. Одна модель на всех превращается в класс-свалку с размытыми словами, где каждое изменение ломает чужие правила.
  • Ограниченный контекст — явная граница, внутри которой модель и язык однозначны. У контекста свои язык, модель, код, данные, команда-владелец и вход-выход; между контекстами — только идентификаторы и явный перевод.
  • Поддомены обнаруживают, контексты проектируют. В учебной школе соответствие один к одному, в жизни контекст бывает шире поддомена (купленная платформа, старая система) или поддомен режут на несколько контекстов.
  • Границы ищут по языку, экспертам, процессам и данным; режут по возможностям бизнеса, а не по существительным. По закону Конвея у контекста один владелец, а команды выстраивают по границам контекстов.
  • Контекст — не обязательно микросервис: модульный монолит с честными границами лучше пяти сервисов с общей базой. Описывать контекст удобно канвой DDD Crew — от назначения до метрик и открытых вопросов.

Дальше: урок DDD.2.2 — карта контекстов. Контексты не живут поодиночке: Набор зависит от Оплаты, Оплата — от внешней платёжной системы. Разберём шаблоны отношений между контекстами и командами и напишем предохранительный слой, который не пускает модель чужой платёжной системы в код школы.

Закончили урок?Урок пройден

Отметьте его — и 5 карточек из урока попадут в колоду повторения. · 5 карточек — в колоде повторения

Первоисточники 6

  • EN
    Eric Evans — Domain-Driven Design Reference (2015), шаблон Bounded Context
    определение ограниченного контекста: явные границы по командам, частям приложения, коду и схемам данных; почему полная унификация модели большой системы невыгодна. Подробно — глава 14 «синей книги» (часть IV о стратегическом проектировании)пересказ
  • EN
    DDD Crew — The Bounded Context Canvas
    README: разделы канвы (Section Definitions), порядок заполнения, советы по проектированию интерфейса контекста (Design Tips); файл resources/model-traits-worksheet.md — роли контекстовадаптация · CC-BY-4.0
  • EN
    DDD Crew — Domain-Driven Design Starter Modelling Process
    README, шаги Organise (команды по границам контекстов с учётом когнитивной нагрузки) и Define (роли и ответственность каждого контекста)пересказ · CC-BY-4.0
  • EN
    Harry Percival, Bob Gregory — Architecture Patterns with Python («Cosmic Python»), глава 7
    файл chapter_07_aggregate.asciidoc, врезка «Aggregates, Bounded Contexts, and Microservices»: «клиент» значит разное для продаж, логистики и поддержки; в модели — только данные, нужные для её расчётовпересказ · CC-BY-ND
  • EN
    Vlad Khononov — Learning Domain-Driven Design (O’Reilly, 2021), глава 3
    глава «Managing Domain Complexity»: поддомены обнаруживают, контексты проектируют; размер контекста; одна команда может владеть несколькими контекстами, но не наоборот. Рус. пер. — «Изучаем DDD — предметно-ориентированное проектирование»пересказ
  • EN
    Martin Fowler — BoundedContext (bliki)
    короткая заметка с картинкой двух контекстов, в которых одни и те же слова значат разноессылка

Лицензия текста урока: CC-BY-SA-4.0 · обновлено 2026-10-06