Подтвердить что ты не робот

Методы обработки аномальной модели домена

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

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 для операций "чистой базы данных" и отдельных бизнес-помощников для более конкретных бизнес-операций?
    • Для этого лучше использовать нестатические или статические вспомогательные классы?
    • Плюсы: объекты домена не привязаны к какой-либо базе данных/бизнес-логике (полностью анемии)
    • Минусы: не очень OO, не очень естественно использовать помощников в коде приложения (выглядит как код C)
  • Используйте метод Double Dispatch, где у объекта есть методы для сохранения в произвольном хранилище
    • Плюсы: лучшее разделение проблем
    • Минусы: объекты имеют некоторую дополнительную логическую привязку (хотя она развязана)
  • В С# 3.0 вы можете использовать методы расширения для присоединения CRUD/бизнес-методов к объекту домена, не касаясь его
    • Это действительный подход? Какие плюсы и минусы?
  • Другие методы?

Каковы наилучшие методы обработки этого? Я новичок в DDD (я читаю книгу Эванса - возможно, это откроет мне глаза)

4b9b3361

Ответ 1

Маркус Фаулер много писал о моделях доменов, в том числе моделях анонимных доменов. Он также имеет краткие описания (и диаграммы классов UML) многих шаблонов проектирования для моделей и баз данных домена, которые могут быть полезны: Каталог "Шаблоны архитектуры корпоративных приложений" .

Я бы предложил посмотреть Active Record и Data Mapper шаблоны. Из описания вашей проблемы звучит так, что ваши вспомогательные классы содержат как правила домена/бизнеса, так и данные о реализации базы данных.

Активная запись переместила бы логику вспомогательного домена и код базы данных в другие объекты домена (например, ваш базовый класс Entity). Data Mapper переместил бы логику вспомогательного домена в объекты домена и код базы данных в отдельный объект карты. Любой подход был бы более объектно-ориентированным, чем вспомогательные классы процедурного стиля.

Книга Эрика Эванса "Домен Driven Design" превосходна. Он становится немного сухим, но определенно стоит того. В InfoQ есть мини-книга с мини-книгой, разработанная под управлением домена, которая является хорошим введением в книгу Эванса. Плюс "Быстрое создание кода домена" доступно в виде бесплатного PDF файла.

Ответ 2

Чтобы избежать анемичной модели, реорганизовать свои вспомогательные классы:

Логика вроде:
"Customer.PurchaseProduct(продукт продукта, оплата платежа)",
"Customer.KillCustomer(Человек-убийца, Оружие оружия)"
должны существовать прямо в объекте домена "Клиент".

Логика вроде:
"Customer.IsCustomerAlive()"
"Customer.IsCustomerHappy()"
должны соответствовать спецификациям.

Логика вроде:
"Customer.Create()",
"Customer.Update()"
очевидно, должны идти в хранилища.

Логика вроде:
"Customer.SerializeInXml()"
"Customer.GetSerializedCustomerSizeInBytes()"
следует обратиться к услугам.

Комплексные конструкторы должны идти на заводы.

Что я вижу. Я был бы рад, если бы кто-нибудь мог прокомментировать мое понимание подхода DDD.


Edit:

Иногда - не следует избегать анемичной модели домена .

Отредактировал мой ответ, чтобы добавить, что DDD - это не собирание и удаление шаблонов.
DDD о том, как мы думаем.

Ответ 3

Я всегда думал о модели анемичного домена как анти-шаблоне. Это ясно, что клиент будет покупать продукты, что способность может быть создана с помощью реализации интерфейса

Interface IPurchase
      Purchase(Product);

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

Ответ 4

Один из подходов, который вы не упомянули, использует AOP для обработки вашего доступа к данным. Пример моего недавнего использования этого подхода (хотя и значительно упрощенного для целей публикации) состоял в том, что у меня был объект домена Account, который имел метод debit, инкапсулируя бизнес-логику, необходимую для успешного дебетования из учетной записи.

N.B. Весь код - это 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);
}

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