Проектирование, ориентированное на предметную область:Избегание анемичных областей и моделирование ролей в реальном мире

StackOverflow https://stackoverflow.com/questions/6824032

Вопрос

Я ищу несколько советов о том, насколько сильно мне следует беспокоиться о том, чтобы избегать анемичной доменной модели.Мы только начинаем работать с DDD и боремся с параличом анализа в отношении простых проектных решений.Последний момент, которого мы придерживаемся, - это то, к чему относится определенная бизнес-логика, например, у нас есть Order объект, обладающий такими свойствами, как Status и т.д.Теперь предположим, что я должен выполнить команду типа UndoLastStatus поскольку кто-то допустил ошибку с заказом, это не так просто, как просто изменить Status поскольку другая информация должна быть занесена в журнал и свойства изменены.Теперь, в реальном мире, это чисто административная задача.Итак, на мой взгляд, у меня есть два варианта, которые я могу придумать:

  • Вариант 1:Добавьте метод для заказа, чтобы что-то вроде Order.UndoLastStatus(), хотя это в некотором роде имеет смысл, на самом деле это не отражает предметную область.Также Order является основным объектом в системе, и если все, что связано с order, будет помещено в класс order, ситуация может выйти из-под контроля.

  • Вариант 2:Создать Shop объект, и при этом имеют разные службы, которые представляют разные роли.Так что я мог бы Shop.AdminService, Shop.DispatchService, и Shop.InventoryService.Так что в этом случае я бы имел Shop.AdminService.UndoLastStatus(Order).

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

Это было полезно?

Решение

Вариант 2 наверняка приведет к процедурному коду.
Возможно, его проще разработать, но гораздо сложнее поддерживать.

Теперь, в реальном мире, это чисто административная задача

Задачи "Администрирования" должны быть частными и вызываться посредством публичных, полностью "доменных" действий.Предпочтительно - по-прежнему написанный простым для понимания кодом, который управляется из домена.

На мой взгляд, проблема в том, что UndoLastStatus для эксперта в предметной области это имеет мало смысла.
Скорее всего, речь идет о создании, отмене и исполнении заказов.

Что-то в этом роде могло бы подойти лучше:

class Order{
  void CancelOrder(){
    Status=Status.Canceled;
  }
  void FillOrder(){
    if(Status==Status.Canceled)
      throw Exception();
    Status=Status.Filled;
  }
  static void Make(){
    return new Order();
  }
  void Order(){
    Status=Status.Pending;
  }
}

Лично мне не нравится использование "статусов", они автоматически передаются всем, кто их использует - я вижу это как ненужное соединение.

Так что у меня было бы что-то вроде этого:

class Order{
  void CancelOrder(){
    IsCanceled=true;
  }
  void FillOrder(){
    if(IsCanceled) throw Exception();
    IsFilled=true;
  }
  static Order Make(){
    return new Order();
  }
  void Order(){
    IsPending=true;
  }
}

Для изменения связанных вещей при изменении состояния заказа лучше всего использовать так называемый события домена.
Мой код будет выглядеть примерно так:

class Order{
  void CancelOrder(){
    IsCanceled=true;
    Raise(new Canceled(this));
  }
  //usage of nested classes for events is my homemade convention
  class Canceled:Event<Order>{
    void Canceled(Order order):base(order){}
  }     
}

class Customer{
  private void BeHappy(){
    Console.WriteLine("hooraay!");
  }
  //nb: nested class can see privates of Customer
  class OnOrderCanceled:IEventHandler<Order.Canceled>{
   void Handle(Order.Canceled e){
    //caveat: this approach needs order->customer association
    var order=e.Source;
    order.Customer.BeHappy();
   }
  }
}

Если заказ становится слишком большим, возможно, вы захотите проверить, что ограниченные контексты являются (как говорит Эрик Эванс - если бы у него был шанс написать свою книгу снова, он бы перенес ограниченные контексты в самое начало).

Короче говоря, это форма декомпозиции, основанная на предметной области.

Идея относительно проста - это нормально иметь несколько Заказов с разных точек зрения, то есть контекстов.

Например.- Заказ из контекста покупки, Заказ из контекста учета.

namespace Shopping{
 class Order{
  //association with shopping cart
  //might be vital for shopping but completely irrelevant for accounting
  ShoppingCart Cart;
 }
}
namespace Accounting{
 class Order{
  //something specific only to accounting
 }
}

Но обычно достаточный домен сам по себе избегает сложности и легко поддается декомпозиции, если вы прислушаетесь к нему достаточно внимательно.Например.Вы можете услышать от экспертов такие термины, как OrderLifeCycle, OrderHistory, OrderDescription, которые вы можете использовать в качестве якорей для декомпозиции.

Примечание:Имейте в виду - я ничего не понимаю о Вашем домене.
Вполне вероятно, что те глаголы, которые я использую, совершенно незнакомы ему.

Другие советы

Я бы руководствовался следующим СХВАТИТЬ принципы.Примените Эксперт по информации принцип проектирования заключается в том, что вы должны возложить ответственность на класс, который, естественно, обладает наибольшим количеством информации, необходимой для выполнения изменения.

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

Также смотрите Фасад закономерность.

Я думаю, что наличие такого метода, как UndoLastStatus, в классе Order кажется немного неправильным, потому что причины его существования в некотором смысле выходят за рамки order.С другой стороны, наличие метода, который отвечает за изменение статуса заказа, Order.ChangeStatus, прекрасно вписывается в модель предметной области.Статус заказа - это надлежащая концепция домена, и изменение этого статуса должно выполняться через класс Order, поскольку он владеет данными, связанными со статусом заказа - класс Order несет ответственность за поддержание своей согласованности и надлежащего состояния.

Другой способ думать об этом заключается в том, что объект Order - это то, что сохраняется в базе данных, и это "последняя остановка" для всех изменений, примененных к Заказу.Легче рассуждать о том, каким может быть допустимое состояние для заказа, с точки зрения Заказа, а не с точки зрения внешнего компонента.Именно в этом суть DDD и ООП: людям легче рассуждать о коде.Кроме того, для выполнения изменения состояния может потребоваться доступ к закрытым или защищенным элементам, и в этом случае лучшим вариантом является использование метода в классе order.Это одна из причин, по которой к анемичным моделям предметной области относятся неодобрительно - они перекладывают ответственность за поддержание согласованности состояния на класс-владельца, тем самым нарушая инкапсуляцию, среди прочего.

Одним из способов реализации более специфичной операции, такой как UndoLastStatus, было бы создание OrderService, который предоставляет доступ к домену и показывает, как внешние компоненты работают с доменом.Затем вы можете создать простой командный объект, подобный этому:

class UndoLastStatusCommand {
  public Guid OrderId { get; set; }
}

У OrderService был бы метод для обработки этой команды:

public void Process(UndoLastStatusCommand command) {
  using (var unitOfWork = UowManager.Start()) {
    var order = this.orderRepository.Get(command.OrderId);
    if (order == null)
      throw some exception

    // operate on domain to undo last status

    unitOfWork.Commit();
  }
}

Итак, теперь модель домена для Order предоставляет все данные и поведение, соответствующие Заказу, но OrderService и уровень обслуживания в целом объявляют различные виды операций, которые выполняются над заказом, и предоставляют домен для использования внешними компонентами, такими как уровень представления.

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

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

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