Как я могу полагаться на это (или даже то, что возможно), во многом зависит от того, как вы собираетесь его удерживать. Например, вы используете 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 лет, но я надеюсь, что это поможет кому-то там.
Еще один вопрос, касающийся строк, относящихся к правилу № 3, заключается в том, применяются ли эти правила только к доменному уровню или применяются также к слоям приложения или представления. Примером может быть использование ViewModel в парадигме MVVM для обертывания элементов внутри агрегата для целей презентации; это нарушит правило № 3? – jpierson
@MylesRip Когда корневой объект предоставляет внешнему объекту ссылку на его внутренний дочерний объект, он не дает ссылку на объект. Он дает идентификатор внутреннего дочернего объекта и только идентификатор. Внешние не могут ссылаться на методы непосредственно на этом внутреннем дочернем объекте. Он должен перейти в репозиторий, получить корневой объект и вызвать методы в корневой сущности. – omittones