Влад Хононов

Что такое предметно-ориентированное проектирование?


(What is Domain-Driven Design?)

Глава 4
Сопоставление контекстов
Использование паттерна ограниченного контекста не только обеспечивает согласованность единого языка, но также даёт возможность моделирования. Нельзя создать модель, не определив её цель — её границу. Граница разделяет ответственность языков. В одном ограниченном контексте единый язык может моделировать предметную область для решения одной конкретной проблемы. В другом ограниченном контексте эти же бизнес-сущности могут использоваться для решения другой проблемы.

Кроме того, модели в разных ограниченных контекстах могут эволюционировать и реализовываться независимо. Однако сами ограниченные контексты не являются независимыми. Систему нельзя построить из независимых компонентов: для достижения целей системы они должны взаимодействовать друг с другом. То же самое относится и к ограниченным контекстам. Несмотря на то что их реализации могут развиваться независимо, они должны интегрироваться друг с другом. В результате между контекстами всегда будут появляться точки соприкосновения — контракты.

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

В этой главе вы узнаете о паттернах предметно-ориентированного проектирования, которые определяют способы интеграции и отношений между ограниченными контекстами. Эти паттерны определяются исходя из типа взаимодействия между командами, которые работают над ограниченными контекстами. Мы разделим паттерны на три группы, каждая из которых представляет тип командного взаимодействия: сотрудничество, заказчик-поставщик и раздельные пути.
Сотрудничество
Паттерны сотрудничества подходят для ограниченных контекстов, реализованными командами со стабильной хорошей коммуникацией.

Это требование хорошо выполняется для ограниченных контекстов, реализованных одной и той же командой. Оно также применяется к командам, имеющим взаимозависимые цели, где успех одной команды связан с успехом другой. Опять же, основным критерием здесь является качество коммуникации и взаимодействия команд.

Давайте рассмотрим два паттерна предметно-ориентированного проектирования, подходящие для сотрудничающих команд: паттерны партнёрства и общего ядра.
Партнёрство
В модели партнёрства интеграция между ограниченными контекстами координируется специфическим для каждого случая образом. Первая команда может просто уведомить вторую об изменении в API и вторая команда будет сотрудничать и адаптироваться, как показано на рисунке 4-1. Никакой драмы или конфликтов.

Рисунок 4-1. Партнерство

Интеграция согласовывается двумя сторонами. Ни одна из команд не навязывает язык, используемый для определения контрактов. Команды могут разрешить разногласия и выбрать наиболее подходящее решение. Кроме того, команды сотрудничают при решении возникающих проблем. Ни одна из команд не заинтересована в подавлении другой.

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

Обратите внимание, что этот паттерн может не подойти для территориально разделённых команд из-за возможного возникновения проблем с синхронизацией и коммуникацией.
Общее ядро
Общее ядро является более официальным способом определения контракта между несколькими ограниченными контекстами. Здесь, вместо специфических для каждого случая интеграций, контракт определяется явно в скомпилированной библиотеке — общем ядре. Библиотека определяет методы интеграции и язык, используемые двумя ограниченными контекстами, как показано на рисунке 4-2.

Рисунок 4-2. Общее ядро

Несколько ограниченных контекстов ссылаются на общее ядро. Каждая команда может свободно изменять скомпилированную библиотеку, определяющую интеграционный контракт. Изменение контракта может нарушить сборку другой команды, поэтому, как и в случае партнерства, этот паттерн требует высокого уровня ответственности и синхронизации между командами.

Особенностью паттерна общего ядра является то, что он, в какой-то степени, противоречит основному принципу того, что только одна команда может владеть ограниченным контекстом. Здесь мы извлекаем общую часть из нескольких ограниченных контекстов в собственный ограниченный контекст. В результате общий ограниченный контекст принадлежит нескольким командам.

Ключевым моментом в реализации паттерна общего ядра является сохранение объёма общего ядра небольшим и ограниченным только интеграционным контрактом.
Одна команда владеет несколькими
ограниченными контекстами
Следует отметить, что общее ядро отлично подходит для интеграции ограниченных контекстов, которые реализует и обслуживает одна команда. В подобном случае специфическая интеграция ограниченных контекстов может со временем «стирать» границы контекстов. Общее ядро может быть использовано для явного определения интеграционного контракта.

Более того, в этом сценарии принцип «одна команда — один контекст» не нарушается: оба ограниченных контекста реализованы одной и той же командой.
Заказчик-поставщик
Вторая группа паттернов взаимодействия, которые мы рассмотрим — «заказчик-поставщик». Как показано на рисунке 4-3, здесь один из ограниченных контекстов — поставщик — предоставляет услугу для своих клиентов. Поставщик услуги является «вышестоящим», а клиент или заказчик — «нижестоящим».

Рисунок 4-3. Заказчик–поставщик

В отличие от паттерна сотрудничества, обе команды (поставщик и заказчик) могут успешно действовать независимо. Таким образом, в большинстве случаев у нас имеется дисбаланс сил: либо команда поставщика, либо команда заказчика могут определять интеграционный контракт.

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

Если нижестоящая команда может принять модель вышестоящей, то отношения между ограниченными контекстами называются конформист. Команда-потребитель подстраивается под модель команды-поставщика, как показано на рисунке 4-4.

Рисунок 4-4. Конформист

Решение команды-потребителя отказаться от части своей автономии может быть оправдано по нескольким причинам. Например, контракт, предоставленный командой-поставщиком, может быть стандартом отрасли, устоявшейся моделью или просто отлично подходить под потребности команды потребителя.

Следующий паттерн рассматривает случай, когда потребитель не готов принять модель поставщика.
Паттерн предохранительного уровня (Anticorruption Layer)
Как и в случае с паттерном конформизма, баланс сил в этом отношении всё ещё смещен в сторону поставщика. Однако в данном случае ограниченный контекст потребителя не готов подстраиваться. Вместо этого он может преобразовать модель ограниченного контекста поставщика в модель, адаптированную под свои потребности, с помощью предохранительного уровня, как показано на рисунке 4-5.

Рисунок 4-5. Предохранительный уровень

Паттерн предохранительного уровня рассматривает сценарии, в которых нежелательно или нецелесообразно соответствовать модели поставщика, такие как:

  • Когда нижестоящий ограниченный контекст содержит основную предметную подобласть. Модель основной предметной подобласти требует дополнительного внимания, а соблюдение модели поставщика может затруднить моделирование области задач.
  • Когда модель поставщика плоха или неудобна. Если ограниченный контекст пытается адаптироваться к беспорядку, он рискует стать таким же беспорядком. Это часто бывает в случае интеграции с устаревшими системами.
  • Когда контракт поставщика часто меняется и потребитель хочет защитить свою модель от таких частых изменений. С предохранительным уровнем изменения в модели поставщика затрагивают только механизм перевода.

С точки зрения моделирования, перевод модели поставщика изолирует потребителя от чуждых концепций, которые не имеют отношения к его ограниченному контексту. Таким образом, он упрощает единый язык и модель потребителя.
Сервис с открытым протоколом
(Open-Host Service)
Этот паттерн рассматривает случай, когда баланс сил смещен в сторону потребителя. Поставщик заинтересован в защите своих потребителей и предоставлении максимально качественного обслуживания.

Чтобы защитить потребителей от изменений в его реализации, поставщик услуг связывает свою модель реализации с публичным интерфейсом. Таким образом разрывается связь между моделью реализации поставщика и публичными моделями на разных уровнях, как показано на рисунке 4-6.

Рисунок 4-6. Сервис с открытым протоколом

Общедоступный интерфейс поставщика не предназначен для соответствия его единому языку. Вместо этого он предназначен для предоставления удобного протокола для потребителей, выраженного на языке интеграции. Таким образом, общедоступный протокол называется «публичным языком».

С какой-то точки зрения, паттерн сервиса с открытым протоколом является противоположностью паттерна предохранительного уровня: вместо потребителя перевод его внутренней модели реализует поставщик.
Раздельные пути
(Separate Ways)
Последний вариант взаимодействия — полное его отсутствие. Этот паттерн может применяться по разным причинам, например, в случаях, когда команды не готовы или не могут сотрудничать. Мы рассмотрим несколько причин далее.
Проблемы коммуникации
Частая причина избегания взаимодействия — это трудности коммуникации, обусловленные размером организации или внутренними политическими вопросами. Когда команды имеют трудности с общением и согласованием, эффективным решением для них становится идти своими путями и дублировать функциональность в нескольких ограниченных контекстах.
Универсальная подобласть
Характер дублированной предметной подобласти также может быть причиной для команды идти своим путём. А именно, когда речь идет об универсальной предметной подобласти: если общее решение легко интегрировать, то более эффективно будет интегрировать его в каждом из ограниченных контекстов на локальном уровне. Примером может служить система логирования: бессмысленно предоставлять её как сервис в одном из ограниченных контекстов, если добавленная сложность интеграции такого решения перевешивает преимущества отсутствия дублирования функциональности в нескольких контекстах. Дублирование функционала будет менее затратным, чем взаимодействие.
Различия моделей
Разница в моделях ограниченных контекстов также может быть причиной для расхождения путей. Модели могут быть настолько разными, что нельзя создать конформистские отношения, а создание предохранительного уровня будет дороже, чем дублирование функциональности. В таком случае, снова более эффективно будет идти своими путями.
Когда избегать
Паттерна раздельных путей следует избегать при интеграции основных предметных подобластей. Дублирование реализации таких подобластей будет нарушением стратегии компании по реализации их наиболее эффективным и оптимизированным способом.
Карта контекста
После анализа паттернов интеграции между ограниченными контекстами системы мы можем отобразить их на карте контекста, как показано на рисунке 4-7.

Рисунок 4-7. Карта контекста

Карта контекста — это визуальное представление ограниченных контекстов системы и интеграций между ними. Эта визуальная нотация дает ценное стратегическое представление о разных уровнях:

Высокоуровневый дизайн
Карта контекста предоставляет обзор компонентов системы и моделей, которые они реализуют.

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

Организационные вопросы
Наконец, карта контекста дает представление об организационных вопросах. Например, что, если все потребители определённой команды поставщика прибегают к реализации предохранительного уровня или если все реализации паттерна модели раздельных путей сосредоточены вокруг одной команды?
Заключение
Ограниченные контексты не являются независимыми. Они должны взаимодействовать друг с другом. Способы интеграции ограниченных контекстов определяются следующими паттернами коммуникации:

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

Общее ядро
Контракт интеграции представляет собой скомпилированную библиотеку, на которую ссылаются оба ограниченных контекста.

Конформизм
Потребитель подстраивается под модель поставщика услуг.

Предохранительный уровень
Потребитель транслирует модель поставщика услуг в модель, соответствующую своим потребностям.

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

Раздельные пути
Дублировать определённую функциональность дешевле, чем сотрудничать и интегрировать её.

Интеграции между ограниченными контекстами можно отобразить на карте контекста. Этот инструмент обеспечивает понимание высокоуровневого дизайна системы, паттернов коммуникации и организационных вопросов.

Теперь, когда вы узнали об инструментах предметно-ориентированного проектирования для анализа и моделирования предметных областей, время взглянуть на паттерны для реализации этих моделей в коде.
Глава 5
Паттерны реализации бизнес-логики