Создать сводный корень в контексте другого сводного корня

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

Это моя ситуация:

  • У меня два совокупных корня Scenarioа также Step, Я сделал эти AR, потому что они инкапсулируют связанные элементы домена, и каждый AR должен быть в согласованном состоянии.
  • множественный Steps может существовать в контексте Scenario, Они не могут существовать сами по себе.
  • "Имя / натуральный идентификатор" каждого Step в контексте его Scenario должен быть уникальным. Изменения в Scenario не влияет автоматически на его Steps и наоборот (например, Step не волнует, если Scenario изменяет некоторые описания или изображения).
  • Разные Steps из Scenario можно использовать, редактировать и т. д. одновременно.

На данный момент каждый Step содержит ссылку на его Scenario по соответствующему натуральному идентификатору. Scenario класс ничего не знает о своем Steps, поэтому он не содержит коллекцию с Step Рекомендации.

Как бы я создал новый Stepдля данного Scenario?

  1. Должен ли я загрузить сценарий и вызвать что-то вродеcreateNewStep(...) в теме? Это не будет обеспечивать ограничение уникальности (это на самом деле бизнес-ограничение, а не техническое), потому что Scenario не знает о своем Steps, Мне, вероятно, придется пойти с какой-то "моделью автономного домена" или передать хранилище или сервис методу для выполнения проверок.
  2. Должен ли я использовать службу домена, которая применяет ограничение, запрашивает хранилище и, наконец, создает и возвращает Step?
  3. Должен Scenario просто знать о своем Steps? Я думаю, что хотел бы избежать этого, так как это создало бы уродливые для поддержания двунаправленных отношений.

Можно представить другие варианты использования, такие как Step должны быть классифицированы по вариантам, которые предоставляются конкретным Scenario, В этом случае и если бы не было никаких ограничений относительно "сбора" StepsЯ бы, наверное, пошел с первым "решением". Затем еще раз: если впоследствии будет изменена классификация, потребуется доступ к сценарию для проверки разрешенных классификаций. Это подводит меня к возможному 4-му решению:

  1. Использование некой "комбинации" некоторых возможных решений. Было бы хорошей идеей создать службу домена (доступ ко всему необходимому) и использовать ее в качестве аргумента метода, который нуждается в этом? Затем метод будет вызывать службу, где это необходимо, и "логика домена" остается в сущности / модели.

Заранее спасибо!


Я просто отредактирую вместо того, чтобы копировать вставить ответ;)

Спасибо всем за ваши ответы!:)

Отталкивание шагов назад в сценарий приведет к некоторым довольно большим объектам, которых я пытаюсь избежать (текущее работающее приложение действительно страдает от этого). Кажется, что это в значительной степени похоже на Scrum-пример Вонса "Эффективный дизайн агрегатов", где он использует DomainServices для получения небольших агрегатов (я действительно не знаю, почему я так не уверен в использовании доменных сервисов). Похоже, мне придется использовать доменные сервисы или разбить агрегаты на "StepName" и "StepDetails", как предложено.

1 ответ

Решение

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

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

"Имя / натуральный идентификатор" каждого шага в контексте его сценария должно быть уникальным

Это классическая проблема проверки набора.

Первое, что нужно сделать, это оспорить предположения в вашей модели

Ваша модель - книга рекордов для "имени"? Если ваша модель не является авторитетом, вы должны быть очень осторожны с введением ограничений. Понимание границ авторитета вашей модели действительно важно.

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

Разумно ли отклонять изменение имени при принятии других изменений шага? Это действительно вариант предыдущего - можно ли разделить задачи на две разные команды (одна с именем, другая - нет), которые могут быть успешными или неудачными независимо?

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

Если вы думаете о проблеме с точки зрения реляционной модели, мы смотрим на кортеж (sceneId, name, stepId), и ограничение говорит, что (sceneId, name) образует уникальный ключ. Это намек на то, что имя шага относится к сценарию. В коде эта подпись выглядит как структура данных сценария, которая включает Map<ScenarioName, ScenarioId>,

Конечно, это не обязательно решит все ваши проблемы, но это шаг к согласованию модели с вашим реальным бизнесом.

Когда это не работает...

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

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

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

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

Но имейте в виду: часть идеи доменного проектирования заключается в том, что мы можем оттолкнуть бизнес, когда код говорит нам, что сама бизнес-модель неверна. Где анализ затрат и выгод?

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

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

Другие вопросы по тегам