Strategic Design: Context Map
Как контексты общаются между собой: Partnership, Customer-Supplier, ACL, Conformist, Open Host Service, Published Language, Separate Ways.
TL;DR
- Context Map — карта связей между Bounded Contexts. Каждая связь — политическое решение, а не только техническое.
- 7 типов связей от Partnership (тесное сотрудничество) до Separate Ways (не пересекаемся вовсе).
- ACL (Anti-Corruption Layer) — защита вашей модели от навязанной чужой. Оправдан при интеграции с legacy.
- Conformist — принять чужую модель как есть. Дёшево на старте, дорого в долгосрочной перспективе.
Зачем это учить
В прошлой главе мы разобрали Bounded Context — семантическую границу одной модели. Но система редко состоит из одного контекста. Реальность — 5–20 контекстов, которые обмениваются данными и вызовами.
Как они связаны? Кто кому подчиняется? Кто чью модель импортирует? Это и есть Context Map — карта отношений между контекстами.
Context Maps are more about the relationships between the teams that develop the Bounded Contexts than they are about the software itself.
Vernon подчёркивает: Context Map — про людей и организации, а не только про код. Каждая связь — политическое решение: «кто может диктовать свой язык другому».
7 типов отношений между контекстами
Evans в DDD (2003) выделил семь стандартных паттернов. Мы разберём все.
1. Partnership
Две команды тесно сотрудничают. Изменения планируются совместно. Обе стороны отвечают за успех интеграции.
- Команды садятся вместе на планирование.
- Изменения контракта — по договорённости.
- Синхронное развитие.
Когда работает: проекты с общей ответственностью, стратегически важные интеграции. Когда ломается: одна команда двигается быстрее — вторая тормозит. Партнёрство превращается в бутылочное горлышко.
2. Shared Kernel
Часть модели (типы, VO, базовые правила) явно разделяется между несколькими контекстами. Изменения — только совместно.
- Пример:
Money,EmailAddress,PhoneNumber— Value Objects, используемые всеми контекстами. - Живут в общем пакете
shared/kernel/или отдельной библиотеке. - Изменение требует согласия всех контекстов.
3. Customer-Supplier
Один контекст (Supplier) поставляет данные или сервис другому (Customer). Customer — заказчик, Supplier — исполнитель. Customer определяет требования, Supplier обязан их выполнять.
- Асимметричные отношения — customer главный.
- Supplier делает контракт удобным для customer.
- Требует backlog и планирования на обеих сторонах.
Когда работает: внутри одной компании, когда customer имеет вес. Например, Sales-контекст диктует, что нужно Order-контексту. Когда ломается: supplier не хочет подстраиваться, или customer не имеет полномочий. Тогда — Conformist.
4. Conformist
Downstream-контекст принимает язык и модель upstream-контекста как есть. Никаких переводов и переговоров.
- Дёшево на старте — не тратим на трансляцию.
- Дорого в долгосрочной перспективе — чужие термины протекают в вашу модель.
- Изменения upstream ломают downstream без предупреждения.
Когда оправдан:
- Внешний партнёр — крупная организация, договориться нельзя (банк, гос-система).
- Модель upstream разумна, полностью удовлетворяет ваши нужды.
- Downstream маленький и простой, стоимость трансляции больше выгоды.
Когда бьёт:
- Ваш core становится «продолжением» чужой модели.
- Через 2 года вы не можете поменять внутренние абстракции — они завязаны на upstream.
5. Anti-Corruption Layer (ACL)
Уже разбирали в главе Ubiquitous Language.
Изоляционный слой между вашим контекстом и внешним. Транслирует внешнюю модель в внутренний Ubiquitous Language. Ваш core говорит на своём языке независимо от внешних.
- Обязателен при работе с legacy.
- Обязателен, если ваш язык принципиально другой.
- Стоит дорого — маппинг, дополнительные тесты, дублирование моделей.
Choice ACL vs Conformist — часто основной архитектурный выбор при интеграции.
6. Open Host Service
Контекст публикует стабильный protocol/API, доступный всем внешним consumer’ам. Один контракт — много consumer’ов.
- Публичный REST/GraphQL API, официальное SDK.
- Внутренние изменения не должны ломать API.
- Требует явных версий, deprecation policy.
Примеры: Stripe API, Twilio API, Auth0 API — весь их бизнес построен на стабильном OHS.
Внутри — можно менять что угодно. Контракт — стабильный.
7. Published Language
Формальный, документированный язык обмена между контекстами. Обычно основан на индустриальном стандарте.
- HL7 в медицине, ISO 20022 в финансах, GS1 в ритейле.
- Не «наш DTO», а признанный внешний стандарт.
- Позволяет договариваться с многими сторонами через один язык.
Комбинируется с OHS. OHS + Published Language — сильная стратегия: публичный API говорит на стандарте, понятном всей отрасли.
8. Separate Ways
Явно решаем: между этими двумя контекстами нет интеграции. Каждый занимается своим, дублирование не проблема.
- Стоимость интеграции больше пользы.
- Домены разные, пересечение случайно.
- Например: HR и Accounting — оба знают про сотрудников, но HR не нужно знать про зарплаты в реальном времени.
Часто недооценённое решение. «Не интегрироваться» — валидный ответ. Больше связей ≠ лучше система.
Пример: Context Map e-commerce
Customer-Supplier
Sales ─────────────────▶ Fulfillment
│ │
│ Customer-Supplier │ Customer-Supplier
▼ ▼
Billing Shipping
│ │
│ Published Language │
└────────────┬─────────────┘
▼
ISO 20022 (payments)
▲
│ ACL
Legacy ERP
Marketing ○ ○ ○ Sales
(Separate Ways)
Разбор:
- Sales → Fulfillment (Customer-Supplier). Sales диктует, что нужно передавать при оформлении заказа.
- Sales → Billing (Customer-Supplier). Sales определяет формат событий OrderPlaced.
- Billing → внешний банк (Published Language). Общаются через ISO 20022.
- Sales → Legacy ERP (ACL). ERP говорит на своём языке — Sales изолирует его через ACL.
- Marketing ○ Sales (Separate Ways). Marketing работает с потенциальными клиентами, Sales — с фактическими. Пересечение косвенное.
Как выбрать связь
Простой decision tree:
- Да → рассмотреть Partnership или Customer-Supplier.
- Нет → следующий шаг.
- Да → ACL.
- Нет → следующий шаг.
- Да → Conformist.
- Нет → следующий шаг.
- Да → Separate Ways.
- Нет → пересмотреть архитектуру.
Могу ли я договориться с upstream?
Хочу ли я защитить свой язык от чужого?
Готов ли я принять чужую модель как есть?
Может ли пересечение быть просто исключено?
Как не надо
1. Общий Kernel из всей domain модели
shared/
├── models/
│ ├── user.py # !! Всеми используется
│ ├── order.py # !! Всеми используется
│ ├── product.py # !!
│ ├── customer.py # !!
│ └── payment.py # !!
shared/models/ содержит все ключевые сущности. Каждый контекст импортирует их. Любое изменение затрагивает всех. Distributed monolith.
Правильно: каждый контекст владеет своей моделью. shared/kernel/ содержит только универсальные примитивы (Money, DateRange).
2. Conformist по инерции
Часто Conformist применяется не как осознанный выбор, а «потому что так проще». Через год ваш core в терминах чужого upstream’а. Внесение изменений в upstream ломает вас без предупреждения.
Правильно: явно назвать связь. «Мы Conformist по отношению к внешнему API» — задокументировать это решение, знать риски, планировать миграцию когда/если нужно.
3. ACL везде
Обратная крайность: ACL перед каждой интеграцией, даже если чужая модель совпадает с нашей.
Правильно: ACL — там, где действительно есть разрыв языков. Если внешний контракт разумен и стабилен — Conformist или прямой мапинг достаточен.
4. Отсутствие Context Map
Самая распространённая ошибка — Context Map не нарисован. Люди работают, интегрируют, но никто не знает, как всё связано. Через год — Big Ball of Mud из микросервисов.
Правильно: Context Map должен быть визуальным артефактом, обновляемым при добавлении контекстов и интеграций. Simon Brown’овский C4 Model может помочь.
Trade-offs
| Связь | Скорость на старте | Долгосрочная стоимость | Кто главный |
|---|---|---|---|
| Partnership | Средняя | Высокая (нужно координироваться) | Обе стороны |
| Shared Kernel | Высокая | Средняя (риск разрастания) | Все, кто использует |
| Customer-Supplier | Средняя | Средняя | Customer |
| Conformist | Высокая | Высокая (протечка модели) | Upstream |
| ACL | Низкая (дорого) | Низкая (независимость) | Свой контекст |
| Open Host Service | Низкая (проектирование API) | Низкая (стабильность) | Supplier контекст |
| Published Language | Низкая (стандарт нужно изучить) | Низкая | Индустрия |
| Separate Ways | Высокая | Низкая | Никто |
В твоём же коде
Анонимизированный микросервис интегрируется с:
- marketplace-api (внешний): используется как секондари port. Отношение — Conformist или тонкая ACL, зависит от того, насколько формат marketplace соответствует нашему языку.
- matcher-service (внутренний consumer): нам передаётся формат, который читает
PositionAttributeMapper. Отношение — Customer-Supplier, где matcher — customer.
Здесь важный урок: _adapt_for_consumer в use case — это симптом Customer-Supplier без ACL. Мы просто перекладываем данные в формат, который просит matcher. Правильно — вынести маппинг в отдельный OutgoingMessageMapper (легковесный ACL на исходящей стороне), чтобы наш core говорил на своём языке независимо от matcher-контракта.
Дальнейшее чтение
- Eric Evans. Domain-Driven Design. Part IV, Chapter 14 — Maintaining Model Integrity.
- Eric Evans. DDD Reference. Сжатый справочник со всеми паттернами Context Map.
- Vaughn Vernon. Domain-Driven Design Distilled. Chapter 4 — Context Mapping.
- Chris Richardson. Microservices Patterns. Chapter 2 — как Bounded Context ложится на декомпозицию микросервисов.
- Nick Tune. Bounded Context Design Canvas. Практический инструмент проектирования.
- Alberto Brandolini. EventStorming — метод коллективного открытия контекстов и связей.
Проверьте себя
Мини-quiz · закрепить
Проверьте себя
Q1. Context Map — это в первую очередь...
Q2. Conformist оправдан, когда...
Q3. Что такое Shared Kernel?
Q4. В чём разница Open Host Service и Published Language?
Q5. Separate Ways — это...