Я думаю, то, как обеспечить это (или даже то, что возможно), во многом зависит от того, как вы собираетесь его настаивать. Например, вы используете NHibernate, что, я думаю, означает, что все в доменном объекте должно быть доступно, чтобы его можно было сопоставить с Event Sourcing, где единственное, что имеет значение для восстановления состояния объекта, это сами события, которые упрощает восстановление внутренних объектов, недоступных через открытый интерфейс.
- Корневая сущность имеет глобальную идентичность и в конечном итоге отвечает за проверку инвариантов.
Корневые сущности имеют глобальную идентичность. Объекты внутри границы имеют локальную идентичность, уникальную только в пределах совокупности.
:: Я использую GUID для идентификации. Никогда не используйте ПК. Когда-либо. Мне также довелось использовать GUID для любых некорневых объектов, но вы также можете использовать строку.
- Ничто за пределами Агрегатной границы не может содержать ссылку на что-либо внутри, кроме корневой сущности. Корневая сущность может передавать ссылки на внутренние сущности другим объектам, но они могут использовать их только временно (в пределах одного метода или блока).
:: Здесь важна постоянная реализация. Я получаю события, я могу использовать события для перестройки объектов в корне, которые недоступны общедоступному интерфейсу корня. Поэтому в C # я просто отмечаю все объекты без полномочий root как внутренние, и весь доступ к ним осуществляется через открытый интерфейс пользователя root. Поскольку мой домен находится в собственной сборке, ни один клиент не может получить ссылку на некорневые объекты, и компилятор не позволит мне это сделать случайно. Если мне нужно выставить свойства, я просто гарантирую, что они доступны только для чтения / получения. Если вы используете ORM, то это может быть невозможно, я не уверен. Если вы можете предоставить доступ к internal
для NHibernate, то это может открыть некоторые двери, но это по-прежнему ограничивает вас во многих аспектах. Моим решением в этом случае было бы создание пары методов, которые имитируют моментальный снимок события (то, что было бы, если бы вы были источником событий), которые по существу выделяют состояние, содержащее DTO, которое может использовать NHibernate, и также принимают тот же DTO восстановить состояние объекта. Если возможно, убедитесь, что они доступны только для хранилища.
Изнутри домена (объекты домена, ссылающиеся на другие объекты домена) просто становится дисциплиной (проверенный код), что некорневые объекты должны присутствовать только внутри корней. Если вы правильно настроили свои пространства имен, вы можете использовать проверку зависимостей Visual Studio, чтобы предотвратить сборку проекта при нарушении этого правила.
- Только агрегатные корни могут быть получены напрямую с помощью запросов к базе данных. Все остальное должно быть сделано через обход.
:: Я отмечаю свои некорневые объекты с помощью IEntity, у которого просто есть идентификатор как часть интерфейса. Затем я создаю абстрактный класс AggregateRoot, который реализует IEntity. Это подчиняется характеристике «Совокупный корень - это сущность в совокупности». Тогда мои репозитории только принимают или возвращают экземпляры AggregateRoot. Это обеспечивается абстракцией репозитория с использованием обобщений в качестве ограничений, поэтому его нельзя нарушить без некоторого очевидного шеннаниганри. Смотрите следующий комментарий для "traversal"
- Объекты в Агрегате могут содержать ссылки на другие корни Агрегата.
:: Ключевое слово "ссылки". Это на самом деле просто означает удостоверение личности. Так, например, когда вы «добавляете» экземпляр RootB в RootA, тогда RootA должен захватывать только идентификатор RootB, и он таким образом сохраняется. Так что теперь, если вам нужно вернуть RootB от RootA, вам нужно попросить RootA предоставить вам идентификатор, а затем использовать его для поиска RootB в следующем запросе.
- Операция удаления должна удалить все в пределах совокупной границы одновременно
:: Это довольно просто, но также очень зависит от бизнес-ситуации.Например, допустим, что через рут я создал конфигурацию.В результате настройки было создано несколько файлов ресурсов.Если я удаляю конфигурацию через корень, то эти файлы ресурсов также должны быть удалены.В большинстве случаев, если ваша корневая персистентность настроена правильно, это позаботится о себе.Однако, с точки зрения инвариантов, вы можете столкнуться с чем-то более сложным.Например, если у вас был управляющий объект, который был корневым, и у этого менеджера было много подчиненных, то, удалив менеджера, может потребоваться много действий для завершения процесса в бизнес-терминах.Например, возможно, этим сотрудникам необходимо обнулить поле «отчеты».Это более сложная тема, потому что здесь задействовано много факторов проектирования системы.Например, являетесь ли вы источником событий, является ли это системой, управляемой событиями, или синхронной и т. Д. Для решения этой проблемы могут быть сотни различных способов.Я думаю, что основной момент здесь заключается в том, что Aggregate Root отвечает за то, чтобы это произошло, или, по крайней мере, за то, что процесс запущен.
- Когда зафиксировано изменение какого-либо объекта в пределах границы Aggregate,все инварианты всего агрегата должны быть выполнены.
:: См. предыдущий комментарий о менеджерах и сотрудниках.По сути, это просто означает, что перед сохранением корня должны быть применены все бизнес-правила.Я применяю это, убедившись, что когда вы запускаете ActionA (), если какое-либо бизнес-правило не выполняется в агрегате или его некорневых объектах, или объектах значений или НИЧЕГО вдоль линии, я выкидываю исключение.Это предотвращает окончательную фиксацию, поскольку первоначальное действие () никогда не завершается.Чтобы это работало, вы должны убедиться, что ваш обработчик (независимо от того, что запускает это действие) не пытается сохранить преждевременно.Чтобы эмулировать транзакцию, я обычно жду до самого конца операции (или цепочки операций), прежде чем пытаться что-либо сохранить.Если ваш ограниченный контекст является хорошим, аккуратным, вам действительно нужно сохранить только одну сущность (корень) в конце операции, так как он является корнем.
В некоторых случаях у вас может быть несколько корней для сохранения, но вам придется выяснить, как откатить эту транзакцию.Те снимки, которые я упомянул, могут сделать это тривиальным.Например, вы получаете root A и B и сохраняете их снимки (сувениры), затем выполняете операцию.Затем вы пытаетесь сохранить RootA, и это успешно.Вы пытаетесь сохранить RootB, но выдается исключение (может быть, соединение не удается или что-то).После некоторой неудачной логики повторения вы используете моментальный снимок для восстановления RootB, а затем сохраните его, а затем сбросьте исключение, чтобы оно появилось в журналах как фатальное исключение.Если по какой-то причине вы не можете восстановить и сохранить RootA (база данных сейчас не работает - дрянное время), то вы просто записываете память из своего журнала, чтобы позднее ее можно было восстановить вручную (например, поставить ее в очередь для восстановления).Некоторым не нравится идея создания исключений в домене для нарушения бизнес-правил, и они утверждают, что для этого следует использовать события (см. Исключения должны быть исключительными), и я не согласен.Мне просто удобнее с этим подходом на данный момент.Есть миллион способов сделать это, но на самом деле это не проблема DDD, я просто предлагаю несколько идей о том, как можно использовать конструкцию для решения этих неизбежных вопросов / проблем.
Я знаю, что это 8годы спустя, но я надеюсь, что это поможет кому-то там.