Эта статья является переводом материала «Mapper Contexts & Supercontexts: Decoupling Domain-Specific and Domain-Generic Bounded Contexts».

Далее не будут переводится следующие фразы:

  • Notifications – Контекст (сервис) уведомлений

  • Domain-Specific – Предметно-ориентированный

  • Domain-Generic – Предметно-неспециализированный

  • Generic – неспециализированный

  • Domain Mapper Context – Контекст сопоставления доменов

  • Self-service – самообслуживание

  • Published language – общедоступный язык

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

Первый разработчик предлагает модель push: ограниченный контекст должен дать указание Notifications отправить уведомление. Notifications должен просто подчиняться командам и отправлять указанные уведомления.

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

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

Наличие навыков DDD в команде является большим преимуществом. Возможность проанализировать свой домен, чтобы понять основные (core), вспомогательные (supporting) и generic возможности, позволяет вам найти разумные технические компромиссы.

Давайте подробнее рассмотрим этот сценарий с точки зрения DDD.

Анализ возможностей предметной области

Аргумент в пользу первого варианта (pushed commands) заключается в том, что Notifications (domain-generic ограниченный контекст) не должен зависеть от domain-specific ограниченного контекста. Если что-то является общим для многих доменов, логически неправильно, если оно связано с чем-то в одном конкретном домене.

Если выйти за рамки абстрактных рассуждений, о чем предупреждает нас эта эвристика? О каких технических последствиях нам нужно знать?

Есть два сценария, которых мы хотим избежать:

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

  2. Невозможность повторного использования или замены generic контекстов готовыми решениями из-за слишком тесной связи с domain-specific ограниченными контекстами.

Связывание доменных и generic обязанностей

В первом варианте нет совместного изменения между domain-specific и domain-generic API. Если требуется новое уведомление, изменяются только domain-specific контексты. Но это не относится к варианту 2.

Во втором варианте (на основе хореографии), если вводится новое событие, для которого требуется уведомление, domain-specific API должно будет опубликовать новое событие, а Notifications должен будет подписаться на событие и отправлять уведомление. Это кажется неправильным – знание предметной области скрывается внутри generic контекста.

Изолирование generic возможностей

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

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

Итак, отправка команды от domain-specific к domain-generic контекстам – это лучшая практика?

Все данные свидетельствуют о том, что первый вариант верен: domain-specific контексты должны отправлять команды в domain-generic контексты, чтобы отделить их от логики домена. Однако наш анализ был поверхностным. Нам необходимо продолжить анализ предметной области, чтобы дать нам уверенность в том, что мы делаем правильный технический выбор.

Более глубокий анализ предметной области

Рассматривая более внимательно первый вариант, каждый domain-specific контекст, который должен отправлять уведомления, взял на себя дополнительную ответственность. Он должен знать, когда отправлять уведомление и какого типа.

Разумно ли размазывать логику уведомлений по всем ограниченным контекстам? Вместе с запутанным кодом это может привести к необходимости координации между многими командами, если изменится подход к уведомлениям.

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

Некоторые концепции предметной области четко разделены (красные, серые, желтые треугольники, синие круги), но некоторые из них могут быть классифицированы несколькими способами (синие треугольники)
Некоторые концепции предметной области четко разделены (красные, серые, желтые треугольники, синие круги), но некоторые из них могут быть классифицированы несколькими способами (синие треугольники)

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

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

Увеличивая масштаб спорной обязанности, может ли появится отсутствующая концепция? Возможно, существует еще одно понятие, которое связывает domain-specific с domain-generic, чтобы обеспечить более элегантную модель.

Domain Mapper Contexts

Одним из шаблонов, который можно использовать для развязки domain-specific и domain-generic, является Domain Mapper Context. Когда контекст принимает на себя эту роль, он подписывается на определенные события domain-specific контекста и сопоставляет их с командами, которые отправляются в domain-generic контекст.

Обратите внимание, как компромиссы этого паттерна соотносятся с плюсами и минусами первых двух вариантов. Он обеспечивает плюсы обоих — свободу изменять способ отправки уведомлений, не загромождая каждый domain-specific контекст, сложностью, связанной с уведомлениями.

Рассмотрим сценарий: собственный Notifications сервис должен быть заменен готовым решением. Domain Mapper Context перенаправит все команды в новый Notifications сервис без какого-либо влияния на domain-specific контекст.

Рассмотрим другой сценарий: нужно добавить новую нотификацию. Регистрация будет настроена в Domain Mapper.

Notifications – это domain-generic функция. Многие домены используют электронную почту и push нотификации.
Notifications – это domain-generic функция. Многие домены используют электронную почту и push нотификации.
Настройки нотификаций в Twitter — сопоставление domain-specific событий с domain-generic действиями (электронные письма).
Настройки нотификаций в Twitter — сопоставление domain-specific событий с domain-generic действиями (электронные письма).

Почему Mapper Contexts?

Название этого шаблона имеет большое значение. Действия, которые происходят внутри одного домена, сопоставляются с действиями, которые происходят в другом домене (domain-generic контекст является общим для многих доменов, поэтому частично находится в них).

Он может иметь сходство с другими шаблонами, такими как messaging translator, однако преобразование (translation) подразумевает некоторую эквивалентность; преобразованное значение является другим представлением исходного значения. С mapper context это не так.

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

Gateway Domain Mapper Contexts

Если вы решите заменить созданный вами domain-generic контекст решением SaaS, ваш Domain Mapper Context станет Gateway Domain Mapper Context.

Gateway (шлюз) находится на границе системы, управляющей входной и выходной информацией.
Gateway (шлюз) находится на границе системы, управляющей входной и выходной информацией.

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

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

Компромиссы при проектировании Domain Mapper Context

Дополнительный уровень сопоставления (mapping) означает, что теперь задействуются три участника (collaborators). Следовательно, появляется больше узлов в системе, которые могут выйти из строя.

Имеет место также совместное изменение. Когда вводятся новые или изменяются существующие события, необходимо будет изменить как domain-specific контексты, владеющие событиями, так и Mapper Context.

Однако существуют широко используемые решения для минимизации этих затрат.

Self-service Domain Mapper Contexts

Часто встречающийся шаблон — это self-service ограниченный контекст. Ограниченный контекст, играющий эту роль, позволяет потребителям использовать возможности контекста, не будучи заблокированным командой, владеющей контекстом.

Первый вариант - это compile-time (на этапе компиляции) механизм через систему управления версиями, а второй – runtime (во время выполнения) механизм через вызовы API.

В сценарии уведомлений self-service контекст может предоставлять DSL. Команды, владеющие domain-specific контекстами, создали бы запрос на извлечение, содержащий изменения в файле конфигурации (еще лучше - код, который компилируется и тестируется), который, используя DSL, настраивает сопоставление между событием домена для подписки и уведомлением для отправки.

В динамической версии соответствующая конфигурация будет выполняться через вызов API или UI (пользовательский интерфейс). Когда возможна динамическая конфигурация и нет никаких зависимостей от домена во время компиляции, контекст смещается в сторону того, чтобы стать полностью универсальным.

Published Language

Еще один шаблон DDD, который следует учитывать в сценариях такого типа, — это published language. Любое событие, которое инициирует нотификацию, должно быть частью published language.

Published language - это контракт или соглашение о формате сообщений, создаваемых ограниченным контекстом. Обеспечение того, чтобы события, требующие нотификацию, были частью published language, означает, что следует уделять больше внимания обеспечению обратной совместимости и уведомлению потребителей о будущих изменениях.

Должен ли я всегда использовать Domain Mapper Context?

Определенно нет. Каждый из шаблонов, представленных в статье, успешно используется в различных системах. Domain Mapper Context — это еще один шаблон с очевидными компромиссами, который вы можете использовать для изучения альтернативных возможностей моделирования.

Поиск Supercontexts

Домены легко моделировать на наивном или поверхностном уровне. Услышав такое слово, как «нотификации», легко впасть в заблуждение, предполагая, что, поскольку слово звучит как единое понятие, оно должно быть представлено в нашей системе одним ограниченным контекстом.

Когда мы используем DDD для анализа на более глубоком уровне и рассматриваем отдельные domain-specific и domain-generic понятия, часто мы обнаруживаем, что существует несколько способов моделирования предметной области, в том числе наличие нескольких ограниченных контекстов в рамках одного supercontext (суперконтекста).

Развязка domain-specific от domain-generic не связано с созданием красивых абстрактных моделей, которые следуют давним правилам DDD, речь идет о разделении концепций, которые меняются вместе по разным причинам, чтобы мы могли найти компромиссы, которые упрощают развитие систем.

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

Комментарии (1)


  1. funca
    06.03.2022 19:43
    +1

    Интесная тема, хотя по-моему в оригинале читается проще, чем в переводе (что в общем-то беда всей переводной философии).