2010-03-17 4 views
5

При поиске в Интернете, я наткнулся на список правил из книги Эрика Эванса, которые должны быть принуждены для агрегатов:Как следует применять правила для совокупных корней?

  1. Корневая Entity имеет глобальную идентичность и, в конечном счете отвечает за проверку инвариантов
  2. корневых объектов имеют глобальную идентичность. Объекты внутри границы имеют локальное тождество, единственное только внутри Агрегата.
  3. Ничто за пределами границы агрегата не может содержать ссылку на что-либо внутри, кроме корневой сущности. Корневой объект может передавать ссылки на внутренние объекты другим объектам, но они могут использовать их только временно (в рамках одного метода или блока).
  4. Только общие корни могут быть получены непосредственно с запросами базы данных. Все остальное должно быть сделано путем обхода.
  5. Объекты внутри Агрегата могут содержать ссылки на другие корни Агрегата.
  6. Операция удаления должна полностью удалять все в пределах границы совокупности.
  7. Когда происходит переход к любому объекту на границе Агрегата, все инварианты всей совокупности должны быть удовлетворены.

Это все кажется хорошо в теории, но я не вижу, как эти правила будут исполнение в реальном мире.

Возьмите правило 3, например. Как только корневой объект дал внешнему объекту ссылку на внутренний объект, что удерживать этот внешний объект от ссылки за пределами единого метода или блока?

(Если исполнение этого является конкретной платформы, мне было бы интересно узнать, как это будет приведен в исполнение в C# /. NET среды/NHibernate.)

+0

Еще один вопрос, касающийся строк, относящихся к правилу № 3, заключается в том, применяются ли эти правила только к доменному уровню или применяются также к слоям приложения или представления. Примером может быть использование ViewModel в парадигме MVVM для обертывания элементов внутри агрегата для целей презентации; это нарушит правило № 3? – jpierson

+0

@MylesRip Когда корневой объект предоставляет внешнему объекту ссылку на его внутренний дочерний объект, он не дает ссылку на объект. Он дает идентификатор внутреннего дочернего объекта и только идентификатор. Внешние не могут ссылаться на методы непосредственно на этом внутреннем дочернем объекте. Он должен перейти в репозиторий, получить корневой объект и вызвать методы в корневой сущности. – omittones

ответ

-1

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

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

Ваш пример может быть реализован путем маркировки всех совокупных корней с помощью интерфейса маркера «IAggregateRoot» и объектов без полномочий с маркерным интерфейсом «IEntity». Затем ваше пользовательское соглашение FNH будет проверять сущности, помеченные как объекты IEntity, ссылающиеся на объекты IEntity, и когда они будут найдены, будут сигнализировать об ошибке (например, вызывать исключение).

В чем смысл?

+1

Маркировка совокупных корневых объектов с IAggregateRoot и другими объектами с IEntity может быть полезна для управления, какие сущности могут использоваться с шаблоном репозитория, но я не уверен, что это было бы эффективно для обеспечения передовой практики в этой ситуации по двум причинам. 1) Агрегированные объекты могут удерживать ссылки на другие объекты вне границ совокупности (только не наоборот). Тогда это будет действительное использование IEntity, ссылающееся на IEntity. 2) Если разные сущности используются как совокупные корни в разных случаях использования, принудительное исполнение может быть неточным. – MylesRip

+0

Похоже, что «соблюдение правил» для совокупных корней - это менее вопрос определения сущностей таким образом, чтобы соблюдались правила **, а также вопрос образования и вечной бдительности. Это будет справедливое заявление? – MylesRip

6

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

Вы сообщаете своей совокупности, что вы хотите, и это с ней связано.

Если у нас есть агрегат: Автомобиль. Мы не заботимся о бензине и колесах, мы просто едем. Мы спрашиваем автомобиль о вещах, и он отвечает, не давая ссылок на внутренности.

Мы спрашиваем: есть ли у нас бензин? Да. Нет. Дайте мне объект танков, чтобы я мог проверить, есть ли у нас бензин.

+1

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

+0

Моей первой мыслью было бы не просто получить данные и применить правила, а получить данные через объект, который будет использовать данные. Затем объект будет применять правила. Думаю, вам нужно будет рассмотреть каждое действие сверху вниз. Какова цель, как бы вы обернули этот запрос. Пример: Допустим, у нас есть система, чтобы проверить, соответствует ли человек определенному возрасту, а затем открыть дверь, если это правда. Мы будем называть Door.open (Person) Этот метод будет обрабатывать все подзапросы и вызовы, не выставляя данные. –

0

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

0

Как я могу полагаться на это (или даже то, что возможно), во многом зависит от того, как вы собираетесь его удерживать. Например, вы используете NHibernate, который, я думаю, означает, что все в объекте домена должно быть доступно для того, чтобы оно было сопоставлено с Event Sourcing, где единственное, что имеет значение для восстановления состояния объекта, - это сами события, которые упрощает восстановление внутренних объектов, недоступных через открытый интерфейс.

  • Корневой объект имеет глобальную идентичность и в конечном счете отвечает за проверку инвариантов. Корневые объекты имеют глобальную идентичность. Объекты внутри границы имеют локальное тождество, единственное только внутри Агрегата.

:: Я использую идентификатор GUID для идентификации. Никогда не используйте ПК. Когда-либо. Я также использую GUID для любых не-корневых объектов, но вы можете просто использовать строку.

  • Ничто вне границы Агрегата не может содержать ссылку на что-либо внутри, кроме корневой сущности. Корневой объект может передавать ссылки на внутренние объекты другим объектам, но они могут использовать их только временно (в рамках одного метода или блока).

:: Здесь важна реализация настойчивости. Я получаю событие, я могу использовать события для восстановления объектов в корне, которые недоступны в открытом интерфейсе root. Таким образом, в C# я просто отмечаю все не-корневые объекты как внутренние, и весь доступ к ним проксируется через открытый интерфейс root. Поскольку мой домен находится в собственной сборке, ни один клиент не может получить ссылку на не-корневые объекты, и компилятор не позволит мне случайно это сделать. Если мне нужно выставить свойства, я просто гарантирую, что они будут только для чтения/получения только. Если вы используете ORM, то это может быть невозможно, я не уверен. Если вы можете предоставить доступ к internal в NHibernate, тогда это может открыть некоторые двери, но оно по-прежнему ограничивает вас во многих аспектах. Моим решением в этом случае было бы создать пару методов, имитирующих моментальный снимок событий (то, что у вас было бы, если бы вы были источником событий), которые в основном выплюнули состояние, содержащее DTO, которое может использовать NHibernate, а также принимает тот же DTO для восстановления состояния объекта. Если возможно, убедитесь, что они доступны только в репозитории.

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

  • Только базовые корни могут быть получены непосредственно с запросами базы данных. Все остальное должно быть сделано путем обхода.

:: Я отмечаю свои объекты без полномочий с IEntity, который просто имеет идентификатор как часть интерфейса. Затем я создаю абстрактный класс AggregateRoot, который реализует IEntity. Это подчиняется признаку «Совокупный корень - это сущность внутри совокупности». Тогда мои репозитории только принимают или возвращают экземпляры AggregateRoot. Это обеспечивается абстракцией репозитория, используя дженерики в качестве ограничений, поэтому в принципе нельзя нарушать без какого-либо очевидного shhennaniganry.Просмотреть следующие комментарии для "traversal"

  • Объекты внутри Агрегата могут содержать ссылки на другие корни.

:: Ключевое слово «ссылки». Это действительно означает идентификатор. Например, когда вы «добавляете» экземпляр RootB в RootA, тогда RootA должен только фиксировать идентификатор RootB, и он сохраняется таким образом. Итак, теперь, если вам нужно вернуть RootB из RootA, вам нужно попросить RootA дать вам идентификатор, а затем вы будете использовать его для поиска RootB в последующем запросе.

  • операцию удаления необходимо удалить все в пределах агрегированных границ сразу

:: Это довольно прямо вперед, но это также очень сильно зависит от бизнеса-кейса. Например, предположим, что через корень я создал конфигурацию. В результате конфигурации было создано несколько файлов ресурсов. Если я удалю конфигурацию через корень, то эти файлы ресурсов также должны быть удалены. В большинстве случаев, если ваша корневая настойчивость настроена правильно, это позаботится о себе. Однако в терминах инвариантов вы можете столкнуться с чем-то более сложным. Например, если у вас был управляющий объект, который был корневым, и у этого менеджера было много сотрудников, которые сообщали об этом, то, удалив менеджера, для завершения процесса в бизнес-терминах может потребоваться множество действий. Например, возможно, эти сотрудники должны иметь поле «отчеты в». Это более сложная тема, потому что в ней много факторов проектирования системы. Например, вы являетесь источником событий, это система, управляемая событиями или синхронная, и т. Д. Для решения этой проблемы может быть множество разных способов. Я думаю, что основной момент здесь заключается в том, что Aggregate Root отвечает за то, чтобы убедиться, что это происходит, или, по крайней мере, что процесс начался.

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

:: Просмотреть предыдущий комментарий о менеджерах и сотрудниках. Это в основном означает, что до того, как root может быть сохранен, все бизнес-правила должны быть соблюдены. Я соблюдаю это, убедившись, что, если вы запустите ActionA(), если какое-либо бизнес-правило выходит из строя внутри агрегата или его не-корневых объектов или объектов ценности или НИЧЕГО по линии, я выдаю исключение. Это предотвращает окончательную фиксацию, поскольку исходное действие() никогда не завершается. Чтобы это сработало, вы должны убедиться, что ваш обработчик (что бы ни начинал это действие) не пытался сэкономить преждевременно. Чтобы эмулировать транзакцию, я обычно жду до самого конца операции (или цепочки операций), прежде чем пытаться сохранить что-либо. Если ваш ограниченный контекст хорош, аккуратный, вам действительно нужно сохранить только один объект (корень) в конце операции, так как он является корневым.

Есть случаи, когда у вас может быть несколько корней для сохранения, но вам придется выяснить, как сверять эту транзакцию. Те снимки, о которых я говорил, могут сделать это тривиальным. Например, вы получаете root A и B и сохраняете свои снимки (mementos), затем выполняете операцию. Затем вы пытаетесь сохранить RootA, и это произойдет. Вы пытаетесь сохранить RootB, но генерируется исключение (возможно, соединение терпит неудачу или что-то еще). После некоторой неудачной логики повтора вы используете моментальный снимок для восстановления RootB, а затем повторно сохраняете его, а затем восстанавливаете исключение, чтобы оно отображалось в журналах как фатальное исключение. Если по какой-то причине вы не можете восстановить и сохранить RootA (теперь база данных отсутствует - время дерьмовая), тогда вы просто регистрируете память через свой журнал, чтобы впоследствии его можно было восстановить вручную (например, очередь для восстановления). Некоторым не нравится идея бросать исключения в домен для нарушения бизнес-правила и утверждать, что вы должны использовать для этого события (см .: исключения должны быть исключительными), и я не согласен. На данный момент я просто более комфортно отношусь к этому подходу.Есть миллион способов, которыми вы можете это сделать, но это не проблема DDD, я просто предлагаю некоторые идеи о том, как вы могли бы использовать конструкцию для решения этих неизбежных вопросов/проблем.

Я знаю, что прошло 8 лет, но я надеюсь, что это поможет кому-то там.

0

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

Смежные вопросы