Вопрос

Я прочитал некоторые вопросы, касающиеся анемичных моделей предметной области и разделения задач.Каковы наилучшие методы выполнения/присоединения доменной логики к анемичным объектам домена?На моей работе у нас довольно анемичная модель, и в настоящее время мы используем «вспомогательные» классы для выполнения базы данных/бизнес-логики на объектах домена.Например:

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), но он помещает логику предметной области в сущность.

Вот некоторые методы, с которыми я столкнулся, но не уверен, какие из них хорошие/плохие:

  1. Клиент и Продукт наследуются от класса «Entity», который обеспечивает базовые операции CRUD в общем виде (возможно, с использованием ORM).
    • Плюсы:Каждый объект данных автоматически получит операции CRUD, но затем будет привязан к базе данных/ORM.
    • Минусы:Это не решает проблему бизнес-операций над объектами, а также привязывает все объекты домена к базовой сущности, которая может быть неподходящей.
  2. Используйте вспомогательные классы для обработки операций CRUD и бизнес-логики.
    • Имеет ли смысл иметь DAO для операций «чистой базы данных» и отдельных бизнес-помощников для более специфичных для бизнеса операций?
    • Лучше ли использовать для этого нестатические или статические вспомогательные классы?
    • Плюсы:объекты домена не привязаны к какой-либо базе данных/бизнес-логике (полностью анемичны)
    • Минусы:не очень объектно-ориентированный подход, не очень естественно использовать хелперы в коде приложения (похоже на код C)
  3. Используйте технику двойной отправки, при которой у объекта есть методы для сохранения в произвольном репозитории.
    • Плюсы:лучшее разделение задач
    • Минусы:к сущностям прикреплена дополнительная логика (хотя она отделена)
  4. В C# 3.0 вы могли использовать методы расширения для прикрепления CRUD/бизнес-методов к объекту домена, не касаясь его.
    • Это правильный подход?Какие плюсы/минусы?
  5. Другие методы?

Каковы лучшие методы для решения этой проблемы?Я новичок в 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);
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top