Уровень 3 · DDD Глава 11 10 мин

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.

Vaughn Vernon Domain-Driven Design Distilled, 2016

Vernon подчёркивает: Context Map — про людей и организации, а не только про код. Каждая связь — политическое решение: «кто может диктовать свой язык другому».

7 типов отношений между контекстами

Evans в DDD (2003) выделил семь стандартных паттернов. Мы разберём все.

1. Partnership

Определение Partnership

Две команды тесно сотрудничают. Изменения планируются совместно. Обе стороны отвечают за успех интеграции.

  • Команды садятся вместе на планирование.
  • Изменения контракта — по договорённости.
  • Синхронное развитие.

Когда работает: проекты с общей ответственностью, стратегически важные интеграции. Когда ломается: одна команда двигается быстрее — вторая тормозит. Партнёрство превращается в бутылочное горлышко.

2. Shared Kernel

Определение Shared Kernel (общее ядро)

Часть модели (типы, VO, базовые правила) явно разделяется между несколькими контекстами. Изменения — только совместно.

  • Пример: Money, EmailAddress, PhoneNumber — Value Objects, используемые всеми контекстами.
  • Живут в общем пакете shared/kernel/ или отдельной библиотеке.
  • Изменение требует согласия всех контекстов.

3. Customer-Supplier

Определение Customer-Supplier (потребитель-поставщик)

Один контекст (Supplier) поставляет данные или сервис другому (Customer). Customer — заказчик, Supplier — исполнитель. Customer определяет требования, Supplier обязан их выполнять.

  • Асимметричные отношения — customer главный.
  • Supplier делает контракт удобным для customer.
  • Требует backlog и планирования на обеих сторонах.

Когда работает: внутри одной компании, когда customer имеет вес. Например, Sales-контекст диктует, что нужно Order-контексту. Когда ломается: supplier не хочет подстраиваться, или customer не имеет полномочий. Тогда — Conformist.

4. Conformist

Определение Conformist (конформист)

Downstream-контекст принимает язык и модель upstream-контекста как есть. Никаких переводов и переговоров.

  • Дёшево на старте — не тратим на трансляцию.
  • Дорого в долгосрочной перспективе — чужие термины протекают в вашу модель.
  • Изменения upstream ломают downstream без предупреждения.

Когда оправдан:

  • Внешний партнёр — крупная организация, договориться нельзя (банк, гос-система).
  • Модель upstream разумна, полностью удовлетворяет ваши нужды.
  • Downstream маленький и простой, стоимость трансляции больше выгоды.

Когда бьёт:

  • Ваш core становится «продолжением» чужой модели.
  • Через 2 года вы не можете поменять внутренние абстракции — они завязаны на upstream.

5. Anti-Corruption Layer (ACL)

Уже разбирали в главе Ubiquitous Language.

Определение Anti-Corruption Layer (антикорруптивный слой)

Изоляционный слой между вашим контекстом и внешним. Транслирует внешнюю модель в внутренний Ubiquitous Language. Ваш core говорит на своём языке независимо от внешних.

  • Обязателен при работе с legacy.
  • Обязателен, если ваш язык принципиально другой.
  • Стоит дорого — маппинг, дополнительные тесты, дублирование моделей.

Choice ACL vs Conformist — часто основной архитектурный выбор при интеграции.

6. Open Host Service

Определение Open Host Service (открытый хост-сервис)

Контекст публикует стабильный protocol/API, доступный всем внешним consumer’ам. Один контракт — много consumer’ов.

  • Публичный REST/GraphQL API, официальное SDK.
  • Внутренние изменения не должны ломать API.
  • Требует явных версий, deprecation policy.

Примеры: Stripe API, Twilio API, Auth0 API — весь их бизнес построен на стабильном OHS.

Внутри — можно менять что угодно. Контракт — стабильный.

7. Published Language

Определение Published Language (опубликованный язык)

Формальный, документированный язык обмена между контекстами. Обычно основан на индустриальном стандарте.

  • HL7 в медицине, ISO 20022 в финансах, GS1 в ритейле.
  • Не «наш DTO», а признанный внешний стандарт.
  • Позволяет договариваться с многими сторонами через один язык.

Комбинируется с OHS. OHS + Published Language — сильная стратегия: публичный API говорит на стандарте, понятном всей отрасли.

8. Separate Ways

Определение 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:

    Могу ли я договориться с upstream?

    • Да → рассмотреть Partnership или Customer-Supplier.
    • Нет → следующий шаг.

    Хочу ли я защитить свой язык от чужого?

    • Да → ACL.
    • Нет → следующий шаг.

    Готов ли я принять чужую модель как есть?

    • Да → Conformist.
    • Нет → следующий шаг.

    Может ли пересечение быть просто исключено?

    • Да → Separate Ways.
    • Нет → пересмотреть архитектуру.

Как не надо

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 · закрепить

Проверьте себя

  1. Q1. Context Map — это в первую очередь...

  2. Q2. Conformist оправдан, когда...

  3. Q3. Что такое Shared Kernel?

  4. Q4. В чём разница Open Host Service и Published Language?

  5. Q5. Separate Ways — это...