Проектирование на основе доменов: предотвращение анемичных доменов и моделирование ролей реального мира - PullRequest
6 голосов
/ 26 июля 2011

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

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

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

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

Ответы [ 4 ]

6 голосов
/ 27 июля 2011

Вариант 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, которые вы можете использовать в качестве якорей для декомпозиции.

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

0 голосов
/ 13 марта 2012

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

0 голосов
/ 27 июля 2011

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

Еще один способ думать о том, что объект Order - это то, что сохраняется в базе данных, и это «последняя остановка» для всех изменений, примененных к Order.Проще рассуждать о том, каким может быть действительное состояние заказа с точки зрения заказа, а не с точки зрения внешнего компонента.В этом суть DDD и ООП, что облегчает людям понимание кода.Кроме того, для выполнения изменения состояния может потребоваться доступ к закрытым или защищенным элементам, и в этом случае лучшим вариантом будет наличие метода в классе заказа.Это одна из причин, почему анемичные доменные модели не одобряются - они снимают ответственность за поддержание согласованности состояний с классом-владельцем, тем самым нарушая инкапсуляцию среди других вещей.

Один из способов реализации более конкретной операции, такой каккак 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 выставляет вседанных и поведения, которые соответствуют Order, но OrderService и сервисный уровень в целом объявляют различные виды операций, выполняемых над заказом, и предоставляют домен для использования внешними компонентами, такими как уровень представления.

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

0 голосов
/ 26 июля 2011

Я бы руководствовался принципами GRASP . Примените принцип проектирования Information Expert , то есть вы должны распределить ответственность по классу, который, естественно, обладает наибольшей информацией, необходимой для выполнения изменения.

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

Также см. Шаблон Фасад .

...