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

Богатая модель против анемии

Я решаю, следует ли мне использовать модель расширенного домена над моделью анемичного домена, и ищу хорошие примеры из двух.

Я создавал веб-приложения с использованием модели Anemic Domain Model, опирающейся на систему уровня Service → Repository → Storage, используя FluentValidation для проверки BL и помещая весь свой BL в слой Service.

Я прочитал книгу Эрика Эвана DDD, и он (вместе с Фаулером и другими), похоже, считает, что модели анемичных доменов являются анти-паттерном.

Так что я просто хотел получить представление об этой проблеме.

Кроме того, я действительно ищу несколько хороших (базовых) примеров богатой модели предметной области и преимуществ по сравнению с моделью анемичной предметной области, которую она предоставляет.

4b9b3361

Ответ 1

Божидар Божанов, кажется, спорит в пользу анемичной модели в этом сообщении в блоге.

Вот краткое изложение, которое он представляет:

  • объекты домена не должны управляться spring (IoC), они не должны иметь DAO или что-либо, связанное с внедренной в них инфраструктурой

  • Объекты домена имеют объекты домена, которые они зависят от заданного с помощью гибернации (или механизма сохранения)

  • объекты домена выполняют бизнес-логику, поскольку основная идея DDD есть, но это не включает запросы к базе данных или CRUD - только операции с внутренним состоянием объекта

  • редко требуется DTO - в большинстве случаев объекты домена сами являются DTO (что сохраняет какой-то шаблонный код)

  • службы выполняют операции CRUD, отправляют электронные письма, координируют объекты домена, создают отчеты на основе нескольких доменных объектов, выполняют запросы и т.д.

  • Уровень сервиса (приложения) не является тонким, но не включает бизнес-правила, которые являются неотъемлемой частью объектов домена.

  • следует избегать генерации кода. Абстракция, шаблоны проектирования и DI должны использоваться для преодоления необходимости генерации кода и, в конечном счете, для устранения дублирования кода.

ОБНОВЛЕНИЕ

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

Ответ 2

Разница в том, что анемичная модель отделяет логику от данных. Логика часто помещается в классы с именем **Service, **Util, **Manager, **Helper и так далее. Эти классы реализуют логику интерпретации данных и поэтому принимают модель данных в качестве аргумента. Например.

public BigDecimal calculateTotal(Order order){
...
}

в то время как подход с богатым доменом обращает это, помещая логику интерпретации данных в модель богатого домена. Таким образом, он объединяет логику и данные, а модель с богатым доменом будет выглядеть так:

order.getTotal();

Это оказывает большое влияние на согласованность объектов. Поскольку логика интерпретации данных обертывает данные (доступ к данным можно получить только через методы объекта), методы могут реагировать на изменения состояния других данных → Это то, что мы называем поведением.

В анемичной модели модели данных не могут гарантировать, что они находятся в законном состоянии, в то время как в богатой доменной модели они могут. Богатая модель домена применяет принципы OO, такие как инкапсуляция, скрытие информации и совместное использование данных и логики, и поэтому анемичная модель является анти-паттерном с точки зрения OO.

Для более глубокого понимания взгляните на мой блог https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/

Ответ 3

Моя точка зрения такова:

Анемическая модель домена = таблицы базы данных, сопоставленные с объектами (только значения полей, никакого реального поведения)

Богатая модель домена = совокупность объектов, которые вызывают поведение

Если вы хотите создать простое приложение CRUD, возможно, достаточно анемичной модели с классической структурой MVC. Но если вы хотите реализовать какую-то логику, анемичная модель означает, что вы не будете делать объектно-ориентированное программирование.

* Обратите внимание, что поведение объекта не имеет ничего общего с сохранением. Другой слой (Data Mappers, Repositories e.t.c.) отвечает за сохраняющиеся объекты домена.

Ответ 4

Прежде всего, я копирую вставку ответа из этой статьи http://msdn.microsoft.com/en-gb/magazine/dn385704.aspx

На рисунке 1 показана модель анемичного домена, которая в основном представляет собой схему с геттерами и сеттерами.

Figure 1 Typical Anemic Domain Model Classes Look Like Database Tables

public class Customer : Person
{
  public Customer()
  {
    Orders = new List<Order>();
  }
  public ICollection<Order> Orders { get; set; }
  public string SalesPersonId { get; set; }
  public ShippingAddress ShippingAddress { get; set; }
}
public abstract class Person
{
  public int Id { get; set; }
  public string Title { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string CompanyName { get; set; }
  public string EmailAddress { get; set; }
  public string Phone { get; set; }
}

В этой более богатой модели, а не просто разоблачении свойств, которые нужно читать и писать, общественная поверхность Клиента состоит из явных методов.

Figure 2 A Customer Type That’s a Rich Domain Model, Not Simply Properties

public class Customer : Contact
{
  public Customer(string firstName, string lastName, string email)
  {
    FullName = new FullName(firstName, lastName);
    EmailAddress = email;
    Status = CustomerStatus.Silver;
  }
  internal Customer()
  {
  }
  public void UseBillingAddressForShippingAddress()
  {
    ShippingAddress = new Address(
      BillingAddress.Street1, BillingAddress.Street2,
      BillingAddress.City, BillingAddress.Region,
      BillingAddress.Country, BillingAddress.PostalCode);
  }
  public void CreateNewShippingAddress(string street1, string street2,
   string city, string region, string country, string postalCode)
  {
    ShippingAddress = new Address(
      street1,street2,
      city,region,
      country,postalCode)
  }
  public void CreateBillingInformation(string street1,string street2,
   string city,string region,string country, string postalCode,
   string creditcardNumber, string bankName)
  {
    BillingAddress = new Address      (street1,street2, city,region,country,postalCode );
    CreditCard = new CustomerCreditCard (bankName, creditcardNumber );
  }
  public void SetCustomerContactDetails
   (string email, string phone, string companyName)
  {
    EmailAddress = email;
    Phone = phone;
    CompanyName = companyName;
  }
  public string SalesPersonId { get; private set; }
  public CustomerStatus Status { get; private set; }
  public Address ShippingAddress { get; private set; }
  public Address BillingAddress { get; private set; }
  public CustomerCreditCard CreditCard { get; private set; }
}

Ответ 5

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

Пример классов домена с поведением:

class Order {

     String number

     List<OrderItem> items

     ItemList bonus

     Delivery delivery

     void addItem(Item item) { // add bonus if necessary }

     ItemList needToDeliver() { // items + bonus }

     void deliver() {
         delivery = new Delivery()
         delivery.items = needToDeliver()
     }

}

Метод needToDeliver() вернет список предметов, которые необходимо доставить, включая бонус. Он может быть вызван внутри класса, из другого родственного класса или из другого уровня. Например, если вы передаете Order для просмотра, вы можете использовать needToDeliver() для выбранного Order для отображения списка элементов, которые должны быть подтверждены пользователем, прежде чем нажать кнопку сохранения, чтобы сохранить Order.

Ответ на комментарий

Вот как я использую класс домена из контроллера:

def save = {
   Order order = new Order()
   order.addItem(new Item())
   order.addItem(new Item())
   repository.create(order)
}

Создание Order и его LineItem выполняется в одной транзакции. Если один из LineItem не может быть создан, не будет создано Order.

Я имею тенденцию иметь метод, представляющий одну транзакцию, например:

def deliver = {
   Order order = repository.findOrderByNumber('ORDER-1')
   order.deliver()       
   // save order if necessary
}

Все, что находится внутри deliver(), будет выполняться как одна транзакция. Если мне нужно выполнить много несвязанных методов в одной транзакции, я бы создал класс службы.

Чтобы избежать ленивого исключения загрузки, я использую JPA 2.1 с именем entity graph. Например, на экране контроллера для доставки я могу создать метод для загрузки атрибута delivery и игнорировать bonus, например repository.findOrderByNumberFetchDelivery(). На экране бонуса я вызываю другой метод, который загружает атрибут bonus и игнорирует delivery, например repository.findOrderByNumberFetchBonus(). Для этого требуется dicipline, так как я все еще не могу вызвать deliver() внутри бонусного экрана.

Ответ 6

Когда я писал монолитные настольные приложения, я создал богатые модели доменов, которые использовали их для создания.

Теперь я пишу крошечные микросервисы HTTP, там как можно меньше кода, включая анемичные DTO.

Я думаю, что DDD и этот анемичный аргумент относятся к эпохе монолитного рабочего стола или сервера. Я помню эту эпоху, и я согласен с тем, что анемичные модели странны. Я построил большое монолитное приложение для торговли FX, и не было модели, действительно, это было ужасно.

С помощью микросервисов небольшие сервисы с их богатым поведением, возможно, являются составными моделями и агрегатами внутри домена. Поэтому сами реализации микросервиса могут не требовать дополнительного DDD. Приложение микросервиса может быть доменом.

У микросервиса заказов может быть очень мало функций, выраженных как ресурсы RESTful или через SOAP или что-то еще. Код микросервиса заказов может быть чрезвычайно простым.

Дополнительный монолитный одиночный (микро) сервис, особенно тот, который сохраняет модель в ОЗУ, может извлечь выгоду из DDD.

Ответ 7

Вот пример, который может помочь:

Анемический

class Box
{
    public int Height { get; set; }
    public int Width { get; set; }
}

Non-анемия

class Box
{
    public int Height { get; private set; }
    public int Width { get; private set; }

    public Box(int height, int width)
    {
        if (height <= 0) {
            throw new ArgumentOutOfRangeException(nameof(height));
        }
        if (width <= 0) {
            throw new ArgumentOutOfRangeException(nameof(width));
        }
        Height = height;
        Width = width;
    }

    public int area()
    {
       return Height * Width;
    }
}

Ответ 8

Модели аномальных доменов важны для ORM и легкой передачи по сетям (жизненная кровь всех коммерческих приложений), но OO очень важна для инкапсуляции и упрощения "транзакционных/обрабатывающих" частей вашего кода.

Поэтому важно иметь возможность идентифицировать и конвертировать из одного мира в другой.

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

User(AnemicUser au)

и метод адаптера для создания анемичного класса для переноса/сохранения

User::ToAnemicUser() 

Стремитесь использовать ни одного Анемического пользователя везде за пределами транспорта/настойчивости

Ответ 9

Классический подход к DDD не предусматривает, чтобы избежать Anemic vs Rich Models любой ценой. Однако MDA все еще может применять все концепции DDD (ограниченный контекст, карты контекста, объекты значений и т.д.), Но во всех случаях использовать модели Anemic vs Rich. Во многих случаях использование доменных служб для организации сложных случаев использования домена в наборе доменных агрегатов является гораздо лучшим подходом, чем просто агрегаты, вызываемые из прикладного уровня. Единственное отличие от классического подхода DDD - где находятся все проверки и бизнес-правила? Theres новая конструкция, известная как валидаторы модели. Валидаторы гарантируют целостность полной модели ввода до того, как произойдет любой сценарий использования или рабочий процесс домена. Совокупные корневые и дочерние объекты являются анемичными, но каждый из них может иметь свои собственные валидаторы модели, вызываемые при необходимости его корневым валидатором. Валидаторы по-прежнему придерживаются SRP, их легко обслуживать и они могут тестироваться модулем.

Причиной такого сдвига является то, что в настоящее время все больше движется в сторону API-интерфейса, а не UX-первого подхода к микросервисам. REST сыграл в этом очень важную роль. Традиционный подход API (из-за SOAP) изначально был основан на командном API против HTTP-глаголов (POST, PUT, PATCH, GET и DELETE). API, основанный на командах, хорошо сочетается с объектно-ориентированным подходом Rich Model и все еще очень эффективен. Однако простые API-интерфейсы на основе CRUD, хотя они могут вписываться в расширенную модель, гораздо лучше подходят для простых анемичных моделей, валидаторов и доменных служб для организации всего остального.

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