Context mapping
Карта контекстов документирует интеграцию bounded contexts. Паттерны — Shared Kernel, Customer/Supplier, Conformist, ACL, Open Host Service — описывают командные отношения и стратегии трансляции, а не только форму API.
Sales-контекст B2B-платформы вызывал сторонний сервис расчёта налогов. У сервиса была своя модель «продуктов» — он называл их «taxable items» с полями commodityCode, taxJurisdiction и lineNetAmount. Sales-контекст имел собственную модель LineItem с NegotiatedPrice и productId. Разработчик, интегрирующий два контекста, пошёл путём наименьшего сопротивления: он использовал объектную модель налогового сервиса напрямую в Sales-контексте. Класс LineItem обзавёлся полем commodityCode. Сервисный метод Quote, вызывавший налоговый сервис, стал возвращать TaxCalculationResult налогового сервиса напрямую в остальной Sales-контекст. Через три месяца весь Sales-код был усеян полями commodityCode и taxJurisdiction, которые не значили ничего ни для одного доменного эксперта Sales. Когда провайдер налогового сервиса изменил API — заменив taxJurisdiction на enum jurisdictionCode — изменение распространилось в основную доменную модель Sales-контекста. Изменение Sales-фичи требовало понимания объектной модели налогового сервиса. Sales-контекст был заражён языком налогового сервиса. Именно это Эванс называет «коррупцией модели через интеграцию». Когда два bounded context интегрируются без явного слоя трансляции, upstream-модель просачивается в downstream-контекст. Ubiquitous language downstream-контекста постепенно замещается языком upstream. Граница контекста растворяется. Карта контекстов — стратегический инструмент, предотвращающий это: диаграмма и набор именованных паттернов, описывающих не только то, как контексты коммуницируют, но какие властные отношения у них есть и какую стратегию трансляции они используют.
Что такое карта контекстов
Карта контекстов — стратегический обзор bounded contexts системы и отношений между ними. Она отвечает на вопросы:
- Какие контексты существуют?
- Как каждый контекст интегрируется с другими?
- Каковы командные отношения в каждой точке интеграции?
- Какая стратегия трансляции предотвращает коррупцию модели?
Карта контекстов — не архитектурная диаграмма сервисов или API. Это диаграмма команд и моделей. Стрелки на карте контекстов несут метки, описывающие организационные и модельные отношения — Shared Kernel, Customer/Supplier, Conformist, Anticorruption Layer — а не просто «вызывает» или «отправляет событие в».
Карта контекстов B2B-платформы включает: Sales, Billing, Shipping, Product Catalog, Tax Calculation (внешний). Каждая пара контекстов, которые интегрируются, имеет метку отношения. Метка определяет взаимодействие команд и метод трансляции моделей.
Паттерны интеграции
Shared Kernel
Два контекста разделяют небольшое, явно согласованное подмножество доменной модели. Обе команды владеют этой общей частью — изменения в ней требуют координации между обеими командами. Это самая тесная связанность и наибольшие накладные расходы на координацию.
Используйте, когда: два контекста подлинно разделяют ключевой концепт, который дорого дублировать или транслировать. На B2B-платформе value object Money (сумма + валюта) может быть элементом Shared Kernel — он нужен и Sales, и Billing, он стабилен, и наличие двух отдельных реализаций Money, которые могут тихо расходиться, опасно.
Избегайте для: всего, что эволюционирует с разной скоростью в каждом контексте. Shared Kernel, который одна команда хочет менять, а другая нет, создаёт узкое место координации.
Customer/Supplier
Один контекст (Supplier) производит данные или сервисы, которые потребляет другой (Customer). Модель Supplier определяет интерфейс. Customer адаптируется к ней. Но критично то, что отношения согласовываются: Customer может запрашивать изменения, Supplier их рассматривает, и интерфейс эволюционирует при явной координации.
Это наиболее распространённый паттерн между внутренними командами. На B2B-платформе Product Catalog — Supplier для Sales: Sales потребляет данные каталога. Команда Product Catalog поддерживает API; команда Sales может запрашивать дополнения или изменения; команда Catalog расставляет приоритеты и планирует их.
Властные отношения важны. Если команда Supplier игнорирует нужды команды Customer, команда Customer вынуждена обходиться вокруг модели Supplier — что нередко приводит к коррупции модели в Customer-контексте.
Conformist
Customer-контекст не имеет рычагов влияния на Supplier. Supplier не будет менять интерфейс под нужды Customer. Customer просто соответствует: он принимает модель Supplier как есть, включая её язык, формы объектов и жизненный цикл. Ubiquitous language Customer-контекста подчиняется языку upstream.
Это правильный выбор, когда: Supplier — крупная внешняя система, не заинтересованная в ваших потребностях (SaaS-платформа, сторонний поток данных), И модель Supplier достаточно разумна для прямого использования, И стоимость построения слоя трансляции превышает выгоду от сохранения собственной модели.
B2B-платформа может быть Conformist по отношению к платёжному процессору — если API процессора стабилен и его модель «payment», «charge» и «refund» достаточно чисто отображается на концепты Billing, подчинение может быть прагматичным.
Опасность: как только вы становитесь Conformist, вы импортировали ubiquitous language upstream в свой контекст. Ваша модель теперь эволюционирует в темпе Supplier. Дрейф модели в upstream распространяется напрямую в ваш контекст.
Anticorruption Layer (ACL)
Customer-контекст транслирует между моделью Supplier и своей собственной моделью, защищая свой ubiquitous language. ACL — явный компонент трансляции: он живёт на интеграционной границе, говорит на языке Supplier снаружи и на языке Customer внутри.
Именно этот паттерн должен был использовать Sales-контекст B2B-платформы с налоговым сервисом. ACL транслировал бы: LineItem → TaxableItem (при вызове налогового сервиса), TaxCalculationResult → QuoteTaxSummary (при возврате результата в Sales). Модель Sales-контекста никогда не видела бы commodityCode или taxJurisdiction. ACL поглощает трансляцию.
ACL — выбор по умолчанию при интеграции с внешними системами, модель которых вы не контролируете и чей язык существенно отличается от вашего.
▸Почему это работает
Почему «anti-corruption»? Потому что альтернатива — позволить upstream-модели просочиться в ваш контекст — Эванс описывает как «коррупцию» вашей доменной модели. Паттерн Conformist — осознанный выбор принять эту коррупцию, потому что стоимость построения слоя трансляции не оправдана. ACL — явный отказ от этой коррупции: мы потратим инженерные усилия на поддержку собственной модели, а не позволим языку upstream заменить наш. Стоимость ACL реальна (код трансляции для написания, поддержки и тестирования). Выгода — ваш контекст сохраняет свою модель: она эволюционирует в ответ на ваших доменных экспертов, а не на изменения API вашего upstream.
Open Host Service (OHS) и Published Language (PL)
Когда контекст является Supplier для многих downstream-контекстов, он может формализовать свой интеграционный интерфейс как Open Host Service: чётко определённый протокол, с которым может интегрироваться любой downstream без индивидуальных переговоров. OHS стабилен, версионирован и спроектирован для потребления несколькими потребителями.
Published Language — формальный, хорошо задокументированный формат данных, используемый OHS: обычно версионированная схема событий, публичный API-контракт или стандартный формат. На B2B-платформе контекст Product Catalog может предоставлять Published Language из событий товаров (ProductCreated, ProductPriceChanged, ProductDiscontinued) со стабильной схемой, которую потребляют Sales, Billing и Shipping. Схема версионирована; о критических изменениях сообщается заранее.
Паттерн OHS + PL снижает накладные расходы на координацию в отношениях Customer/Supplier при масштабировании — Supplier не договаривается с каждым потребителем индивидуально; он поддерживает опубликованный контракт.
Чтение карты контекстов: власть и трансляция
Самое важное, что коммуницирует карта контекстов, — не топология API, а властные отношения между командами и стратегия трансляции на каждой границе.
Властные отношения: в Customer/Supplier Supplier имеет рычаги влияния. Если Supplier отзывчив и команды хорошо коммуницируют, Customer может эволюционировать. Если Supplier не отвечает (крупная платформенная команда со многими клиентами), Customer в конечном итоге становится Conformist по умолчанию — он не может получить нужные изменения, поэтому адаптирует свою модель под ограничения Supplier.
Стратегия трансляции: ACL означает «мы транслируем на границе, наша модель защищена». Conformist означает «мы приняли upstream-модель». Shared Kernel означает «мы совместно владеем этой частью». Эти выборы имеют накапливающиеся последствия на протяжении лет. Команда, ставшая Conformist десять лет назад, сейчас имеет внутреннюю модель, глубоко связанную с дизайном API upstream-сервиса образца 2015 года.
▸lesson.inset.note
Карта контекстов — не единоразовая диаграмма. Её следует обновлять при добавлении новой интеграции, изменении отношения (Customer/Supplier становится Conformist, потому что Supplier перестаёт отвечать) или разделении/слиянии контекста. Карта — живой документ организационных и архитектурных решений команды. Устаревшая карта контекстов так же вредна, как устаревшие комментарии в коде — она описывает систему, которой больше нет.
Sales-контекст интегрируется с внутренним Inventory-контекстом. Sales-команда хочет проверять наличие товара перед подтверждением Quote. Inventory-команда говорит: «Используйте наш API напрямую — просто вызовите наш эндпоинт InventoryItem и используйте возвращаемый нами enum InventoryStatus». Тимлид Sales говорит: «Нам нужен ACL». Inventory-команда возражает: «Зачем добавлять накладные расходы трансляции для внутреннего сервиса? Это расточительно». Как принять решение?
Product Catalog-контекст публикует событие `ProductPriceChanged` со схемой версии v1. Через шесть месяцев команда Catalog хочет добавить поле `previousPrice` к событию. Sales и Shipping — оба потребители. Что требует от команды Catalog паттерн Open Host Service / Published Language?
Два года назад Billing-контекст стал Conformist по отношению к внутреннему Payments-контексту, импортировав типы `PaymentMethod`, `ChargeResult` и `RefundPolicy` Payments-команды напрямую в доменную модель Billing. Payments-команда теперь куплена другой компанией и их сервис заменяется сторонним процессором. Billing-контексту нужно мигрировать. Почему эта миграция значительно сложнее, чем если бы Billing использовал ACL?
- 01Что такое карта контекстов и что она коммуницирует помимо топологии API?
- 02В чём разница между Conformist и Anticorruption Layer и когда уместен каждый?
- 03Что такое паттерн Open Host Service и как он снижает накладные расходы координации по сравнению с Customer/Supplier?
Коррупция модели из вступительной истории — Sales-контекст, заполненный commodityCode и taxJurisdiction от внешнего налогового сервиса — была не случайностью. Это было предсказуемым результатом интеграции без именованной стратегии. Разработчик пошёл путём наименьшего сопротивления: использовать upstream-модель напрямую. Граница bounded context растворилась.
Карта контекстов именует стратегию в каждой точке интеграции. Shared Kernel: обе команды совместно владеют небольшой частью. Customer/Supplier: согласованный интерфейс, downstream имеет рычаги. Conformist: downstream осознанно принимает upstream-модель. ACL: downstream транслирует на границе и защищает свою модель. Open Host Service: upstream публикует стабильный версионированный контракт для нескольких потребителей.
Каждый паттерн описывает командные отношения, а не только технический облик. Ценность ACL не в избегании кода — а в том, что радиус взрыва изменения upstream — один компонент трансляции, а не вся доменная модель. Опасность Conformist не теоретическая — она материализуется когда модель upstream меняется спустя годы.
Карта контекстов рисуется один раз и непрерывно обновляется. Это стратегическая картина, говорящая каждой команде: вот от кого вы зависите, сколько у вас власти и какова ваша стратегия защиты модели.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.