2015-04-06 7 views
0

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

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

Первое, что приходит мне в голову, - принцип единой ответственности.

Это правда, что путем введения в хранилище AR мы нарушаем SRP, потому что получение и сохранение агрегата не ответственность агрегировать себя. Но в нем говорится только о «агрегате», а не о других агрегатах. Так применяется ли она для извлечения из агрегатов репозитория, на которые ссылается идентификатор? А как их хранить?

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

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

Хорошо, предположим, что мы извлекаем и сохраняем ДРУГИЕ агрегаты изнутри нашего агрегата, используя внедренные репозитории. Каковы другие последствия для нарушения СРП?

Уверен, что существует проблема с отсутствием контроля над сохраняющимися агрегатами, а извлечение - это какая-то ленивая загрузка, что плохо по той же причине (без контроля).

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

Эти причины практически лишают способность внедрять репозиторий в агрегат.

Вот мой главный вопрос: почему мы можем внедрить репозитории в службу домена?

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

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

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

+0

AR должна быть последовательной транзакцией на своем собственном уровне, и не более 2 агрегатов должны быть изменены в одной транзакции. Следовательно, AR, содержащееся в другом, никогда не будет изменено в той же транзакции, в которой он содержит AR. – plalx

+0

Что вы подразумеваете под сделкой? Операция БД начинается и заканчивается методом repository.store, поэтому никаких изменений в других агрегатах не происходит. Или, может быть, вы имеете в виду транзакцию как прецедент? –

+0

Обычно транзакция управляется на уровне службы приложений, а не в репозитории. Вот почему я это сказал. – plalx

ответ

0

Так как я писал в вопросе, я уже нашел свой ответ в процессе написания этого вопроса.

Лучший способ показать это на примере:

Когда мы имеем простое (внешне) поведение как единицы атакующего другую единицу, мы можем написать что-то подобное.

unit.attack_unit(other_unit) 

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

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

Проблема возникает в другом примере.

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

Проблема заключается в том, как мы ее реализуем. Если мы будем использовать уже определенный unit.attack_unit, и мы будем называть его на всех юнитах в игре (итерации по ним), тогда оружие, которое используется для вычисления урона, будет извлекаться из агрегата агрегата, количество раз равное количеству единиц в игре! Но его можно было получить только один раз!

Это не имеет значения, если unit.attack_unit будет метод единичного агрегата, или если это будет обслуживание домена unit_attack_unit. Это будет все то же самое, оружие будет загружено слишком много раз. Чтобы исправить это, нам просто нужно изменить реализацию и, возможно, интерфейс тоже.

Теперь, по крайней мере, у нас есть ответ на вопрос «ли переводит логику из агрегированного метода в службу домена (потому что мы хотим получить доступ к репозиторию там) устраняет проблему?». Нет, это ничего не меняет. Внедрение репозиториев в службу домена может быть столь же опасным, как впрыскивание его в агрегат при неправильном использовании.

Это отвечает на мой вопрос SO, но у нас все еще нет решения реальной проблемы.

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

Один из способов - поместить все необходимые агрегаты в качестве параметров в наш агрегированный метод.

unit.attack_unit(unit, weapon, armor) 

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

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

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

Или, может быть, причина этих проблем - плохое моделирование?

Нападение на другое подразделение действительно несет ответственность за единицу, но несет ли ответственность за ущерб? Конечно нет.

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

So домен ведомый дизайн, получаем исполнение управляемый дизайн. Это то, чего мы хотим? Я так не думаю.

+2

Я думаю, что ваша проблема связана с тем, что вы ссылаетесь на другие агрегаты по id, а не по ссылке. Как правило, хорошей практикой является создание небольших AR, но здесь «Unit» нуждается в ссылке на другие AR, такие как оружие и доспехи **, чтобы сделать свою работу **. Поэтому, вероятно, лучше сохранить ссылку на объект. Затем вы можете просто вызвать 'unit.attack_unit (unitToAttack)'. Этот вызов будет генерировать событие «UnitAttacked», чтобы другие AR были соответствующим образом изменены (например, обновить долговечность оружия, повредить атакующую единицу). – plalx

+1

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

1

Вот места, где я рекомендовал бы принести агрегаты (т.е. называют Repository.Get...()), в порядке предпочтения:

  1. Application Service
  2. домена службы
  3. Совокупные

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

Доменные службы являются ИМО хорошим местом для извлечения агрегатов, когда определяет, какие агрегаты для изменения являются логикой домена как таковой. В вашем примере игры (кстати, это может быть не идеальный контекст для DDD), на какие блоки влияет атака другого подразделения, может считаться логикой домена, поэтому вы можете не захотеть разместить его на уровне Application Service. Однако это редко случается в моем опыте.

И наконец, службы приложений являются местом по умолчанию, где я звоню Repository.Get(...) ради единообразия и потому, что это естественное место, чтобы завладеть актерами прецедента (как правило, только один агрегат за транзакцию) и организовывать вызовы к ним ,

Это не означает, что агрегаты никогда не должны вводиться. Хранилища, есть исключения, но другие альтернативы почти всегда лучше.

+0

хорошо, я полностью согласен с этим. В других приложениях я всегда получаю доступ к репозиторию только в службе приложений, но, как вы видите, это не так просто в игровом контексте. Я думаю, что некоторые правила (например, небольшой агрегат, один агрегат за транзакцию и т. Д.) Не подходят для игр, но это не значит, что DDD в целом плохо для игр. Я должен найти хороший баланс между дизайном, управляемым доменом, и дизайном, ориентированным на производительность, для каждого приложения независимо. К сожалению, нет золотого правила :(В этом примере я думаю, что я просто ссылаюсь на совокупность по ссылке на объект, потому что это не причинит никакого вреда. –

+0

Для игры в реальном времени я определенно рекомендую не использовать DDD для разработки ядра. Для более медленной игры (например, пошаговая система) она может быть лучше подходит, но вы не должны стесняться отклоняться от DDD «ортодоксия» и иногда придумывать, казалось бы, странные конструкции, потому что игры часто подчиняются совсем другим силам, чем ваша обычная линия бизнес-проекта. Такие проекты могут включать в себя большие агрегаты (без проблем параллелизма в пошаговой игре). – guillaume31

0

«Итак, зачем нам нужна уникальная идентификация, если не для сохранения?» - подумайте о сценарии учетной записи, где в вашей системе существует несколько John Smiths. Представьте, что Джон Смит и Джон Смит-младший (которые не вошли в младший в регистрации) живут по тому же адресу. Как вы говорите им обособленно? Представьте, я пытаюсь написать механизм рекомендаций, основанный на их прошлых покупках. , , ,

Идентичность - это качество равенства в DDD. Если у вас нет уникальной уникальной информации из ваших полей, вы являетесь ValueObject.

+0

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

+0

«Очень редко» зависит от домена. Если вы имеете дело с слабосвязанной системой (скажем, мобильный клиент, разговаривающий с службой REST, который также хранит кеш на клиенте в течение не связанных между собой периодов), вы можете заполнить копии объектов в разных местах и ​​должны быть способны сравнить их. Или элементы под совокупным корнем часто требуют поиска конкретного, требующего идентичности, реагировать на событие. Пример будет обновлять количество позиции в заказе. Вы можете претендовать на провод, как настойчивость, но я бы сказал, что для идентификации больше, чем просто. – Mathieson

0

Каковы последствия использования репозитория внутри агрегата и внутри службы домена?

Существует достаточно веский аргумент, что вы также не должны этого делать.

Загадка: когда агрегат должен видеть состояние другого агрегата?

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

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

В этом случае вам не нужно загружать другой агрегат, что делает вопрос «где» спорным.

Два уточнение:

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

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

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

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