Домен-управляемый дизайн и междоменное взаимодействие

10

Я относительный новичок DDD, но я читаю все и вся, что я могу получить, чтобы выкипеть и перевести мои знания.

Я столкнулся с этим вопросом DDD, и один из ответов меня заинтриговал.

DDD ограниченные контексты и домены?

В одном из ответов автор приводит пример системы электронной коммерции, в которой продукты находятся по крайней мере в двух доменах:

1) Каталог продукции 2) Управление запасами

Хорошо, все это имеет смысл, т. Е. В вашей электронной коммерции вы заинтересованы в отображении информации о продукте, а не в управлении запасами.

НО. Возможно, вы захотите отобразить уровень запасов на веб-странице, или вы можете отобразить номер издания инвентаря на складе (представьте, что ваш инвентарь - книги, журналы и т. Д.). Эта информация поступает из домена инвентаризации.

Итак, как бы вы справились с этим? Не могли бы вы

а) Загрузить как домен продукта, так и агрегаты домена инвентаризации? б) Будете ли вы хранить некоторые свойства в вашей доменной сущности Продукта для количества на складе и редакции на складе, а затем использовать события домена, чтобы обновить их при обновлении сущности инвентаризации?

Последний вопрос Я знаю, что мы должны забыть / игнорировать постоянство домена и просто думать о домене. Но просто чтобы обдумать это, в приведенном выше примере мы получим потенциально 2 таблицы БД для каталога товаров и инвентаря товаров. Теперь мы используем тот же идентификатор в них, как и тот же продукт. Или мы могли бы использовать 1 таблицу и 1 строку таблицы для данных и просто отобразить соответствующие данные на совокупные свойства?

PendorPaul
источник

Ответы:

8

Возможно, вы захотите отобразить уровень запасов на веб-странице, или вы можете отобразить номер издания инвентаря на складе (представьте, что ваш инвентарь - книги, журналы и т. Д.). Эта информация поступает из домена инвентаризации.

Главное, на что следует обратить внимание, это то, что вы говорите о представлении, то есть о том, что использование устаревших данных допустимо.

При этом вам не нужно взаимодействовать с агрегатами (которые несут ответственность за предотвращение нарушения изменений бизнес-инварианта), а с представлением недавней копии состояния агрегата.

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

Загрузить как домен продукта, так и агрегаты домена инвентаризации?

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

Будете ли вы хранить некоторые свойства в своей сущности домена Product для количества на складе и издания на складе, а затем использовать события домена для обновления их при обновлении сущности Inventory?

«Не пересекать потоки. Это было бы плохо».

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

Вы хотите сохранить домены в чистоте. Эти приложения , которые взаимодействуют с доменами, это не так важно. Так, например, для приложения Inventory целесообразно вызвать службу в приложении продукта, чтобы запросить некоторые специфические для продукта концепции для добавления в представление. Или наоборот.

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

Но просто чтобы обдумать это, в приведенном выше примере мы получим потенциально 2 таблицы БД для каталога товаров и инвентаря товаров. Теперь мы используем тот же идентификатор в них, как и тот же продукт.

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

Когда это не сработает, тогда вам понадобится какой-то запрос, чтобы восполнить пробел. Я думаю, что наиболее распространенным вариантом этого является то, что более новый объект сохраняет идентификатор более старого объекта. Вы увидите это и в рамках одной БК: заявители, когда они будут одобрены, станут клиентами. Это другой агрегат (состояние, связанное с клиентом, подчиняется инварианту, отличному от состояния кандидата); поэтому, если ваш уровень персистентности использует потоки событий, потоку для нового агрегата потребуется другой идентификатор. Так что где-то будет состояние, в котором говорится, что «этот заявитель стал этим клиентом».

Или мы могли бы использовать 1 таблицу и 1 строку таблицы для данных и просто отобразить соответствующие данные на совокупные свойства?

YIKES! Нет, не делай этого. Вы добавляете конфликт транзакций без какой-либо деловой причины для этого.

VoiceOfUnreason
источник
Я отметил это как ответ, отметьте также @guillaume ниже, который также указал, что чтение данных для отображения в представлении не требует загрузки агрегатов. Спасибо за такой длинный подробный ответ. Исходя из подхода, основанного на «традиционной» модели данных, мне было трудно заставить себя забыть слой постоянства и сосредоточиться на языке предметной области. Когда я думаю, что у меня это есть, я читаю другую статью, которая разрывает мое понимание.
PendorPaul
Я только что прочитал эту статью msdn.microsoft.com/en-us/magazine/dn802601.aspx, в которой подробно описано использование событий домена для дублирования некоторых данных между контекстами. Примером является совместное использование списка клиентов между службой поддержки клиентов и системой обработки заказов. Это дублирует данные клиента в системе заказов и использует события для синхронизации данных. Это противоречит тому, что мы ответили здесь. Конечно, в связанном образце для контекста заказа нужен только идентификатор клиента, и его можно заполнить из приложения, которое имеет доступ к контексту обслуживания клиентов.
PendorPaul
Вы хотите быть очень точным в своем мышлении, здесь. Копирование данных между системами - это НЕ то же самое, что копирование данных между моделями доменов. Каждый раз, когда вы видите «только для чтения [бормотание]», это большой намек на то, что данные не принадлежат этому домену (даже если приложение все еще заботится об этом).
VoiceOfUnreason
Да, я тоже так думал. Сдвиг мыслительного процесса достаточно сложен без публикации «экспертами» статей, которые мутят воду. Я провел сегодня, действительно анализируя мою область, чтобы попытаться и действительно полностью понять, где лежат мои BC и Aggregate Roots. Я в значительной степени там, но также я знаю, что могу улучшить и реорганизовать мою модель, как я иду. Я уже несколько дней зацикливаюсь на анализе, опасаясь начать писать код, опасаясь, что у меня не будет DDD прямо в голове. Я думаю, что лучше всего взломать и продолжить расспрашивать мой код, и является ли модель правильной. Я доберусь туда. Спасибо
PendorPaul
Может быть, я неправильно понял, но практика, с которой вы, по-моему, обращаетесь, называется переносом по событию с переносом (перенос состояния с переносом по событию), и это может быть очень мощной моделью, особенно для представлений панели мониторинга / таблицы, где вам нужно фильтровать и просматривать страницы через услуги. Вы можете создавать «проекции» (в основном представления) из событий домена, чтобы управлять этими инструментальными панелями. Я использовал это раньше довольно успешно. Это может быть очень мощный инструмент в правильном сценарии. ИМХО, что здесь более важно, это то, что вы понимаете, что эти прогнозы не представляют модели предметной области и не должны содержать бизнес-логику.
Иордания
3

Я думаю, что ваш вопрос действительно требует 2 ортогональных набора опций -

  • Вы загружаете два объекта и представляете их данные вместе, или вы загружаете 1 объект, который содержит все, что вы хотите?

  • Вы используете агрегаты для отображения материала или что-то еще?

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

Решение а) от вашего Q, похоже, подвержено множеству этих ловушек. Вариант б) может быть действительным, но я бы использовал его, только если данные изInventoryManagement BC необходимы для принудительного применения инвариантов при мутировании Productагрегата. Лучше, если агрегат содержит все данные, необходимые для проверки своих бизнес-правил после модификации, но на стороне чтения они могут находиться где угодно.

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

О взаимодействиях между BC вы также можете посмотреть на /programming/16713041/communicating-between-two-bounded-contexts-in-ddd

guillaume31
источник
1
ОК, это помогает. По сути, мы используем Aggregate Roots и BC, чтобы гарантировать, что наши инварианты согласованы, и наши данные действительны, а операции, которые мы хотим выполнить, заключены в нашем агрегате или BC. Когда дело доходит до чтения и отображения этих данных, мы можем обрабатывать их отдельно и легко, без необходимости увлажнения всех агрегатов / BC. В конце концов, зачем нам загружать агрегат, когда все, что мы делаем, это отображаем данные в отчете или на экране. Это имеет смысл. Спасибо.
PendorPaul
Вот где CQRS действительно действительно сияет. Вы можете просто выполнить SQL-запрос, объединить таблицы и вернуть специальный запрос для определенного представления. Это также очищает ваш репо от беспорядка метода запроса. Кроме того, вы даже можете реплицировать данные на сервис, такие как ElasticSearch, и запрашивать их.
значения
1

DDD предназначен для приложений, где бизнес-логика сложна. «напечатать что-нибудь» не является сложной бизнес-логикой. На самом деле это совсем не бизнес-логика.

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

Euphoric
источник
Итак, возьмите что-то вроде Amazon, это сложная система со сложной бизнес-логикой. У них есть управление каталогами и управление запасами. Им не нужны итоговые данные инвентаря для управления каталогом, под этим я подразумеваю название продукта, описание, тип, состояние и т. Д. Однако они показывают количество товаров на складе на главной странице своего магазина. В этом сценарии вы хотите разделить домены управления каталогами продуктов и инвентаризации продуктов, но вам необходимо отобразить некоторую информацию об инвентаризации на странице продукта, как вы это делаете?
PendorPaul
Я предполагаю, что я имею в виду, что домен каталога продуктов содержит всю информацию, необходимую для управления каталогом продуктов. Домен Inventory содержит всю информацию, необходимую для управления запасами. Тем не менее, когда я хочу показать некоторую информацию пользователю, и эти данные поступают из 2 доменов, где я могу это сделать. Должен ли я просто загрузить оба домена в моем интерфейсе и связать свойства, которые я хочу показать? Или у меня есть какой-то механизм отчетности / чтения, при котором я возвращаю анонимный тип или DTO, содержащий данные, необходимые для моего пользовательского интерфейса?
PendorPaul
Смешно оглядываться на мои старые комментарии 11 месяцев назад, но кажется, что это целая жизнь. Вы были совершенно правы, Эйфорик, в отношении чтения и записи. Приложение, над которым я работаю, эволюционировало совсем немного по мере развития моего понимания. Сейчас я использую методы CQRS, через Jimmy Bogards Mediatr. Абсолютная гибкость наличия команд, которые взаимодействуют с агрегатами, но затем с помощью запросов и обработчиков запросов возвращают все, что мне нужно для отображения, невероятно. Оберните это в Views, которые обращаются к этим QueryHandlers, и разделение хорошо с этим. Спасибо
PendorPaul
@PendorPaul, я думаю, что я там, где ты был 11 (сейчас 13) месяцев назад. Что привело тебя туда, где ты сейчас?
Грег Белл
1
@GregBell Вы должны изменить мышление. Я застрял в подходе «проектирование базы данных, построение уровня данных, построение некоторой бизнес-логики ... и т. Д.». И я был сосредоточен на создании всех охватывающих сущностей. т. е. «Продукт» на сайте электронной коммерции, который обрабатывает все, начиная от ценообразования, описания, запасов, местоположения запаса… но это становится чрезвычайно сложным. Подход ограниченного контекста означает, что в контексте инвентаризации «Продукт» содержит только информацию и поведение для управления запасами. Описание и изображения управляются в контексте контента, а цены - в контексте цены.
PendorPaul
1

С моей точки зрения, существуют различные определения «продукта» - каждый ограничивающий контекст имеет свое собственное определение «продукта»: домен:

  • В Content-Management-Bounding-Context у продукта есть изображение и текст описания.
  • В контексте ограничения запасов у товара есть количество товара, продавец товара прогнозирует, когда товар будет доступен.
  • В контексте ограничения цены есть правила, сколько может стоить продукт за количество.

Вдобавок к этому я бы добавил дополнительный Shop-Bounding-Context с его собственным определением продукта (соответствующая комбинация доменов продукта других Bounding-Contextxts).

Магазин-продукт будет иметь «изображение и текст описания» из содержимого и наличия в «Инвентаризации», но не «продавец продукта» из инвентаря.

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

k3b
источник
Итак, как вы создаете этот Shop-Product BC? В этом контексте у вас есть ссылка на Product and Inventory BC, и гидрируете ли вы их из своего постоянного хранилища при загрузке Store-Product BC, а затем предлагаете свойства, которые вы хотите от этих BC, через свойства StoreProduct? Я обнаружил, что эта статья на самом деле совпадает с тем, о чем я думал, кросс-события BC, но @ guillaume13 выше указал, что для целей отображения я могу избежать BC и просто получить данные, необходимые для моего просмотра. msdn.microsoft.com/en-us/magazine/dn802601.aspx
PendorPaul