Bounded Context и границы домена
Bounded Context (BC) — это четкая граница, внутри которой действует единый Ubiquitous Language, согласованные модели и инварианты. Внутри границы термины однозначны («Ставка», «Клиент», «Лимит»), а наружу контекст общается контрактами (событиями/командами) и не тянет внутрь чужие смысловые «хвосты». Грамотно выбранные границы снижают связность, упрощают масштабирование и ускоряют эволюцию продукта.
1) Зачем нужны границы
Снижение когнитивной нагрузки. Команда работает с одной моделью и одним языком, а не с «всем бизнесом сразу».
Изоляция инвариантов. Критичные правила (баланс ≥ 0, уникальность логина) живут в одном месте и защищены агрегатами.
Управление изменениями. Эволюция схемы/правил внутри BC не ломает соседа — есть явные контракты.
Производительность и надежность. Внутри BC можно выбрать подходящую модель согласованности и хранилище; снаружи — асинхронные проекции.
2) Как выявлять Bounded Context
Быстрый метод (workshop 2–4 часа):1. Event Storming: выпишите доменные события «что произошло», затем команды «что просим сделать», затем агрегаты «кто гарантирует правило».
2. Кластеры языка: где слова и правила стабильно совпадают — потенциальный BC. Где слово «Клиент» значит разное (плательщик vs игрок) — там явно разные контексты.
3. Инварианты и ownership: что нельзя нарушить и кто отвечает? Инвариант → внутрь того BC, который может его гарантировать.
4. Поток ценности: сгруппируйте шаги, которые часто меняются вместе — это кандидаты на один BC.
5. Орг-структура: если одну часть делает отдельная команда с отдельными KPI — вероятно, это отдельный BC (но не наоборот: оргструктура не должна слепо диктовать модель).
Сигналы границы:- Спор о терминах («ставка», «билет», «раунд» — разные смыслы).
- Самый горячий инвариант «протекает» через сервисы.
- Разные SLO и темп изменений.
- «Dual-write» между модулями ради атомарности.
3) Типичные контексты (пример предметной области)
Identity/KYC — регистрация, уровни верификации, статусы ограничений.
Wallet/Ledger — балансы, проводки, резервы, валюты.
Betting/Orders — прием, котировки, расчет.
Game/Round — жизненный цикл раунда, результаты.
Bonus/Promo — начисления, вейджер, конверсия.
Payments — депозиты/выводы, статусы платежных шлюзов.
Compliance/Reporting — отчеты, аудит, регуляторные витрины.
Catalog/Provider Integration — игры, версии, статусы провайдеров.
Analytics/Read Models — проекции и материализованные представления.
4) Context Map: как BC взаимодействуют
Карта контекстов фиксирует тип отношений:- Customer–Supplier. Один BC (Supplier) поставляет события/данные, другой (Customer) подстраивает свои модели.
- Conformist. Customer принимает язык и модель Supplier как есть (например, нормативный реестр).
- Partnership. Два BC синхронно эволюируют язык и контракты (часто – одна команда/roadmap).
- Shared Kernel. Общий минимальный подъязык/библиотека, версионируется совместно; использовать осторожно.
- Anti-Corruption Layer (ACL). Защитный слой, переводящий чужие модели в свой язык.
- Open Host Service / Published Language. Публичные протоколы/схемы, версионируемые и документированные.
Практика: по умолчанию используйте ACL и асинхронные события; Conformist — только если провайдер диктует стандарт, Shared Kernel — минимально и осознанно.
5) Граница = язык + модель + инварианты + хранилище
Внутри BC определите:- Ubiquitous Language. Словарь терминов с примерами.
- Агрегаты и инварианты. Кто «держит» правила и какие операции разрешены.
- Модель согласованности. Strong/CP для денег, EC/causal для витрин.
- Хранилище и индексы. Выбираются под инварианты и SLO.
- Контракты выхода. События/команды, версии схем, SLO доставки.
Снаружи: никаких прямых SQL/табличных зависимостей. Общение — через контракт.
6) Границы и согласованность (PACELC)
Внутри BC: выбирайте модель под инварианты (Wallet — Strong, Betting — Strong на приеме).
Между BC: чаще всего eventual через события и проекции. Если нужна синхронная проверка — явная команда с дедлайном и отказом при недоступности (не «скрытый» REST-зов).
7) Антикоррупционный слой (ACL)
Задача ACL: не пустить чужой язык и «грязные» данные внутрь BC.
Маппинг схем: внешнее `PaymentStatus=SETTLED` → внутреннее `LedgerEntry(type=Credit, reason=PsPSettle)`.
Валидация и обогащение: проверка инвариантов, нормализация таймзон, валют.
Версионирование: поддержка `schema_version` внешнего контракта, обратная совместимость.
Идемпотентность: по `external_id`/`operation_id`.
Наблюдаемость: трейс-теги `source`, `schema_version`, `mapping_id`, DLQ для «ядовитых» сообщений.
8) Границы и данные: владение, проекции, API
Ownership: кто владеет «истиной»? Только владелец меняет запись. Остальным BC — read-модели и ссылки.
Проекции: денормализованные таблицы под чтения; обновляются из событий.
API: команды (мутируют у владельца) и запросы (читают проекции). Никаких «сквозных» апдейтов чужих данных.
9) Эволюция и версии
События и API — с `schema_version` и политикой совместимости (additive + fallback).
Blue/Green по BC: новый контракт `v2` публикуется параллельно `v1`, трафик переводится постепенно.
Миграции: для серьезных изменений — новая проекция/сервис, «двухфазный свитч» чтений.
10) Тестирование границ
Contract tests: проверка, что BC соблюдает публикуемый контракт (producer tests) и корректно понимает чужой (consumer tests).
Property-based: инварианты агрегатов внутри BC (баланс, лимиты, уникальности).
Chaos на интеграциях: задержки, out-of-order, дубликаты, schema-evolution; наличие DLQ и безопасного редрайва.
NFR-тесты: p95/пиковая нагрузка на границе (сервер событий/ACL).
11) Наблюдаемость и SLO по границам
Метрики: throughput событий/команд, `projection_lag_ms`, `dlq_rate`, ошибки маппинга, p95 API.
Трейсинг: обязательные теги `bc`, `tenant_id`, `event_id`, `operation_id`, `schema_version`.
Алерты: превышение лага проекций, рост отказов команд, «флап» схемы (много `schema_mismatch`).
12) Мульти-тенант и регионы
`tenant_id` — в ключах всех событий и проекций на границе.
Fairness: лимиты на публикации/редрайв per tenant, чтобы «шумный» не срывал SLO соседей.
Residency: данные BC живут в «домашнем» регионе; кросс-регионально — агрегаты/отчеты.
13) Анти-паттерны (к чему приводит размытая граница)
Гигантский «core-service». Все в одном месте → борьба за транзакции, длинные релизы, низкая автономность.
Табличные интеграции. Прямые SELECT в чужие таблицы → хрупкость и coupling по схеме.
Dual-write. Одновременно писать в два BC «для удобства» → расхождения и саботаж инвариантов.
Conformist по умолчанию. «Приняли чужую модель как есть» → утечка чужих смыслов, невозможность эволюции.
Скрытые синхронные вызовы. REST-зов «где-то внутри» без явного контракта и дедлайна → неожиданная зависимость по доступности.
14) Пример контуров (словесная схема)
[Wallet/Ledger] <--CP, Leader, Transactions-->
publishes: WalletReserved/Committed v
[Betting] <--CP on bid taking-->
events: BetPlaced/Settled v
[Read Models/Analytics] <--EC projection-->
[Payments] --ACL--> [Wallet/Ledger]
[Provider Integration] --ACL--> [Game/Round]
[Compliance] <-events - [KYC/Identity], -> reports [Reporting]
15) Мини-гайд по выбору границы
1. Сформулируйте инварианты и определите, кто их может гарантировать.
2. Опишите словарь (10–20 терминов) и проверьте, что у команды одно понимание.
3. Нарисуйте Context Map и типы отношений.
4. Решите модель согласованности внутри и на стыках.
5. Спроектируйте контракты (события/команды) и ACL.
6. Запланируйте наблюдаемость (метрики/трейсинг/алерты) и DLQ/редрайв.
7. Проведите contract-tests и «шторма» (chaos) для интеграций.
8. Зафиксируйте governance: кто владеет языком/схемой, как вносятся изменения.
16) Чек-лист перед продом
- У каждого BC есть словарь, агрегаты и инварианты.
- Определены отношения на Context Map и документированы контракты.
- Интеграции через события/команды и ACL, нет прямых SQL-зависимостей.
- Идемпотентность команд/событий; есть outbox/inbox и DLQ.
- Модель согласованности (внутри/между BC) зафиксирована и протестирована.
- Версионирование схем и стратегия совместимости (v1/v2).
- Метрики лага/ошибок/производительности и алерты настроены.
- Политики мульти-тенантности и data-residency соблюдены.
- Операционные плейбуки: schema-mismatch, redrive, rebuild проекций.
17) Быстрые рецепты
Деньги и лимиты: отдельный BC с CP и транзакциями, API только командами, события как исход истины для чтений.
Фиды/каталоги: BC с EC, проекции и кэш, явный `freshness`.
Интеграции с внешними провайдерами: всегда через ACL, события/команды, версионирование схем.
Рост команды: один BC — одна команда, у команды есть «владелец языка» и «хранитель инвариантов».
Рефакторинг монолита: сначала контракты и ACL, потом физическое разделение.
Заключение
Bounded Context — это не только диаграмма, а рабочий договор о языке, правилах и способе эволюции. Четкие границы уменьшают связность, ускоряют изменения и делают систему предсказуемой в эксплуатации. Разделяйте по смыслу и инвариантам, защищайте границы ACL и контрактами, измеряйте все метриками — и ваша архитектура останется гибкой и надежной даже при стремительном росте домена и команды.