#c# #validation #domain-driven-design #ddd-repositories #user-defined
Вопрос:
Я борюсь с основной проблемой. Проект написан на C#, но проблема носит общий характер.
Я следую принципу всегда действительного объекта. В качестве одного из примеров у меня есть сущность продукта, которая имеет обязательное свойство «ProductCategory». Разрешенные категории продуктов определяются пользователем и, следовательно, сохраняются (в базе данных).
Для безопасности типов и лучшего чтения я определил объекты значений для различных свойств, чтобы инкапсулировать бизнес-правила для этих типов. Таким образом, существует класс ProductCategory. При создании экземпляра ProductCategory метод фабрики проверяет, например, максимальную длину строки, которая передается на фабрику перед созданием экземпляра. Это гарантирует, что каждый экземпляр ProdcutCategory действителен.
Простая проверка параметров, таких как длина, проста и понятна. Мой вопрос заключается в том, где реализовать проверку соответствия возможным значениям, которые сохраняются. Хранилище разрешенных значений скрывает технологию сохранения и имеет метод Exists или isValid.
Вариант 1) вызов репозитория с уровня приложения перед вызовом фабрики.
Здесь уровень домена не зависит от уровня репозитория/инфраструктуры, как утверждают многие. Но фабрика больше не может гарантировать наличие допустимого объекта. Это зависит от уровня приложения, на котором реализовано бизнес-правило. Кроме того, каждая команда, которой нужна категория продукта, должна проверять репозиторий, что нарушает DRY.
Вариант 2) вызов репозитория из фабрики объектов значений
Поскольку фабрики являются частью уровня домена, уровень должен иметь доступ к хранилищу, что создает дополнительную связь. Преимущество заключается в том, что деловая обоснованность категории продукта обеспечивается самим объектом и не может быть обойдена.
Есть ли другое решение этой дилеммы или существуют какие-то конкретные критерии, которые поощряют тот или иной вариант? Было бы нормально, если бы фабрика была запрограммирована на интерфейс репозитория, который сохраняется на уровне домена и реализуется только репозиторием на уровне инфраструктуры? Я пытался понять различные подходы, но в данном случае я чувствую себя потерянным.
Ответ №1:
Если я правильно понимаю, ваш объект значения ProductCategory заботится о своих собственных инвариантах, но может ли конкретная категория продукта быть назначена конкретному продукту, определяется пользователем и хранится в базе данных.
Вы могли бы использовать события домена:
- Вы назначаете Категорию Продукту в модели домена.
- Продукт добавляет событие домена «ProductCategoryAssigned».
- Перед выполнением вашей единицы работы обработчик событий домена выполняет проверку с помощью базы данных, что конкретная категория может быть назначена Продукту, и выдает, если нет.
Это предотвратит завершение транзакции в недопустимом состоянии.
Вы могли бы использовать ValidatorAggregate:
Если вы действительно хотите, чтобы ваш продукт был «всегда действителен», даже до того, как будет выполнена единица работы, тогда вы можете пойти на:
- Создайте группу допустимых категорий и репозиторий, который загружает допустимые категории на основе свойств продукта.
- Метод «SetCategory» вашего продукта (или заводской метод) объявляет допустимую категорию в качестве требуемого параметра, заставляя уровень приложения получать ее перед вызовом заводского метода.
- В вашем заводском методе вы можете вызвать «Допустимые категории».HasCategory(категория)» и выбросьте перед созданием продукта, если его нет.
Старайтесь избегать внедрения репозиториев в вашу доменную модель.
Комментарии:
1. в случае опции «События домена» эта проверка должна быть в процессе и синхронизирована?
2. Используя ValidatorAggregate, что произойдет, если одна и та же категория будет создана (зафиксирована/сохранена) сразу после того, как ваш репозиторий загрузил группу допустимых категорий?
3. @deezg … Да, если у вас есть инфраструктура, которая доставляет команды обработчикам команд на уровне приложений, то она может затем перечислять и обрабатывать события домена (которые были созданы во время обработки команды) в процессе и отправлять их обработчикам событий до выполнения единицы работы.
Ответ №2:
Вариант 2 кажется лучшим.
В этом случае связь не является проблемой, вы связываете свою фабрику с запросом, необходимым для проверки проверки, делая эту зависимость явной.
Обратите внимание, что единственный способ избежать возможной согласованности этих проверок-это поместить все данные в один агрегат. В противном случае невозможно предотвратить тот факт, что одна и та же категория создается сразу после того, как вы выполнили проверку, и до того, как вы совершите транзакцию (это реальная причина агрегирования).
Думайте о возможной согласованности только в том случае, если в этом есть реальная проблема, как часто создается категория? как часто создается продукт? насколько велика вероятность возникновения конфликта? есть ли какая-то реальная проблема, если это произойдет?
В большинстве случаев полная согласованность обходится гораздо дороже, чем принятие окончательной согласованности.