Методы работы с анемичной доменной моделью
-
03-07-2019 - |
Вопрос
Я прочитал некоторые вопросы, касающиеся анемичных моделей предметной области и разделения задач.Каковы наилучшие методы выполнения/присоединения доменной логики к анемичным объектам домена?На моей работе у нас довольно анемичная модель, и в настоящее время мы используем «вспомогательные» классы для выполнения базы данных/бизнес-логики на объектах домена.Например:
public class Customer
{
public string Name {get;set;}
public string Address {get;set;}
}
public class Product
{
public string Name {get;set;}
public decimal Price {get;set;}
}
public class StoreHelper
{
public void PurchaseProduct(Customer c, Product p)
{
// Lookup Customer and Product in db
// Create records for purchase
// etc.
}
}
Когда приложению необходимо совершить покупку, оно создает StoreHelper и вызывает метод для объектов домена.На мой взгляд, для Клиента/Продукта имело бы смысл знать, как сохранить себя в репозитории, но вам, вероятно, не нужны методы Save() для объектов домена.Также имело бы смысл использовать такой метод, как Customer.Purchase(Product), но он помещает логику предметной области в сущность.
Вот некоторые методы, с которыми я столкнулся, но не уверен, какие из них хорошие/плохие:
- Клиент и Продукт наследуются от класса «Entity», который обеспечивает базовые операции CRUD в общем виде (возможно, с использованием ORM).
- Плюсы:Каждый объект данных автоматически получит операции CRUD, но затем будет привязан к базе данных/ORM.
- Минусы:Это не решает проблему бизнес-операций над объектами, а также привязывает все объекты домена к базовой сущности, которая может быть неподходящей.
- Используйте вспомогательные классы для обработки операций CRUD и бизнес-логики.
- Имеет ли смысл иметь DAO для операций «чистой базы данных» и отдельных бизнес-помощников для более специфичных для бизнеса операций?
- Лучше ли использовать для этого нестатические или статические вспомогательные классы?
- Плюсы:объекты домена не привязаны к какой-либо базе данных/бизнес-логике (полностью анемичны)
- Минусы:не очень объектно-ориентированный подход, не очень естественно использовать хелперы в коде приложения (похоже на код C)
- Используйте технику двойной отправки, при которой у объекта есть методы для сохранения в произвольном репозитории.
- Плюсы:лучшее разделение задач
- Минусы:к сущностям прикреплена дополнительная логика (хотя она отделена)
- В C# 3.0 вы могли использовать методы расширения для прикрепления CRUD/бизнес-методов к объекту домена, не касаясь его.
- Это правильный подход?Какие плюсы/минусы?
- Другие методы?
Каковы лучшие методы для решения этой проблемы?Я новичок в DDD (я читаю книгу Эванса, так что, возможно, это откроет мне глаза)
Решение
Мартин Фаулер много писал о моделях предметной области, в том числе анемичные доменные модели.У него также есть краткие описания (и диаграммы классов UML) многих шаблонов проектирования для моделей предметной области и баз данных, которые могут быть полезны: Каталог «Шаблоны архитектуры корпоративных приложений».
Я бы предложил посмотреть Активная запись и Сопоставитель данных узоры.Судя по описанию вашей проблемы, похоже, что ваши вспомогательные классы содержат как доменные, так и бизнес-правила. и подробности реализации базы данных.
Active Record переместит логику домена помощника и код базы данных в другие объекты домена (например, ваш Entity
базовый класс).Data Mapper переместит логику предметной области помощника в объекты предметной области, а код базы данных — в отдельный объект карты.Любой подход будет более объектно-ориентированным, чем вспомогательные классы процедурного стиля.
Книга Эрика Эванса «Дизайн, управляемый предметной областью» великолепна.Немного суховато, но оно того стоит.У InfoQ есть Мини-книга «Быстрое проектирование на основе предметной области» это хорошее введение в книгу Эванса.Кроме того, «Быстрое проектирование на основе домена» доступно в виде бесплатного PDF-файла.
Другие советы
Чтобы избежать анемичной модели, проведите рефакторинг вспомогательных классов:
Логика вроде:
«Клиент.ПокупкаПродукт(Продукт, Оплата платежа)»,
«Customer.KillCustomer(Убийца, Оружие)»
должен существовать прямо в объекте домена «Клиент».
Логика вроде:
«Клиент.IsCustomerAlive()»
«Клиент.IsCustomerHappy()»
следует перейти к спецификациям.
Логика вроде:
"Клиент.Создать()",
«Клиент.Обновление()»
очевидно, следует перейти в репозитории.
Логика вроде:
«Клиент.SerializeInXml()»
«Клиент.GetSerializedCustomerSizeInBytes()»
надо пойти в сервис.
Сложные конструкторы должны идти на заводы.
Вот как я это вижу.Я был бы рад, если бы кто-нибудь прокомментировал мое понимание подхода DDD.
Редактировать:
Иногда — анемичная доменная модель не следует избегать.
Отредактировал свой ответ, добавив, что DDD не касается подбора и удаления шаблонов.
DDD — это то, как мы думаем.
Я всегда считал модель анемичного домена антипаттерном.Понятно, что клиент будет покупать продукты, и эта возможность может быть реализована за счет реализации интерфейса.
Interface IPurchase
Purchase(Product);
, поэтому любой из ваших объектов домена может реализовать это по мере необходимости.Таким образом, вы можете добавить функциональность объектам вашего домена – и это именно то, что и должно быть.
Один из подходов, о котором вы не упомянули, — это использование АОП для управления доступом к данным.Примером моего недавнего использования этого подхода (хотя и значительно упрощенного для целей публикации) было то, что у меня был Account
доменная сущность, у которой был debit
метод, инкапсулирующий бизнес-логику, необходимую для успешного списания средств со счета.
Н.Б.Весь код — Java с нотацией AspectJ AOP…
public boolean debit(int amount) {
if (balance - amount >= 0) {
balance = balance - amount;
return true;
}
return false;
}
Внедрив соответствующий репозиторий в свой аспект, я затем использовал pointcut для перехвата вызовов этого метода...
pointcut debit(Account account,int amount) :
execution(boolean Account.debit(int)) &&
args(amount) &&
target(account);
...и применил несколько советов:
after(Account account, int amount) returning (boolean result) : debit(account,amount) {
if (result) getAccountRepository().debit(account, amount);
}
По моему мнению, это дает хорошее разделение задач и позволяет объектам вашей предметной области полностью сосредоточиться на бизнес-логике вашего приложения.