Проектирование на основе домена - как реализовать всегда действительное состояние - PullRequest
0 голосов
/ 11 февраля 2019

У меня есть модель домена Заказ с OrderItems.

Заказ должен

  1. иметь менеджера
  2. иметь как минимум еще один OrderItem.

Конструктор моего заказа выглядит следующим образом:

public Order(Manager manager, IList<OrderItem> orderItems) 
{
    if(manager == null) throw new ArgumentNullException(nameof(manager));
    if(orderItems == null) throw new ArgumentNullException(nameof(orderItems));
    if(orderItems.Count == 0)
        throw new Exception("List must contain at least one item.");
    foreach(var item in orderItems)
        AddItem(item);
    //assign values
    this.manager = manager;
    ...
    ...
}
Manager manager;
IList<OrderItem> orderItems;
...

void AddItem(OrderItem orderItem)
{
   if(orderItem == null) throw new ArgumentNullException(nameof(orderItem));
   if(orderItems.Contains(orderItem))
       throw new Exception("Order Item duplicate");
   orderItems.Add(orderItem);
}

void CreateNewOrder(int managerId, List<int> itemIdList)
{
    Manager manager = managerRepo.FindById(managerId);
    List<OrderItem> itemList =new List<OrderItem>();
    foreach(int itemId in itemIdList)
        itemList.Add(itemRepo.FindById(itemId));

    Order order = new Order(manager, itemList);
    orderRepo.Add(order);
}

Я думаю, что он близок к модели персистентности, а не к модели предметной области.

Что если я напишу код ниже?

public Order(Manager manager)
{ 
    if(manager == null) throw new ArgumentNullException(nameof(manager));
    this.manager = manager;
    ...
    ...
}

public void AddItem(OrderItem orderItem)
{
   if(orderItem == null) throw new ArgumentNullException(nameof(orderItem));
   if(orderItems.Contains(orderItem))
       throw new Exception("Order Item duplicate");
   orderItems.Add(orderItem);
}

public void ReadyForPersistence()
{
    if(orderItems.Count == 0)
        throw new Exception("Not ready for persistence");
}

void CreateNewOrder(int managerId, List<int> itemIdList)
{
    Manager manager = managerRepo.FindById(managerId);
    Order order = new Order(manager);

    //Here order has zero item, does this mean order is in invalid state?

    foreach(int itemId in itemIdList)
        order.AddItem(itemRepo.FindById(itemId));

    order.ReadyForPersistence(); 
    orderRepo.Add(order);
}

Неужели я неправильно понял «Всегда действительное состояние»?

Как правильно реализовать «всегда действительную модель состояния».

Ответы [ 2 ]

0 голосов
/ 13 мая 2019

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

Хотя в заказе может быть как минимум 1 позиция, в корзине покупок может быть от 0 до n товаров.

Как одно состояние перехода?Ну, вы превращаете сущность в какую-то адресуемую вещь, например, ссылку.После того, как вы сделали объект адресом, вы можете использовать неизменяемые объекты.

Неизменяемый объект - это просто постоянная структура данных, которая после создания не может иметь своего состояния.Скорее вы выполняете функции / методы для него, которые возвращают измененную копию исходного объекта с примененным изменением.Это означает, что вызов любой данной функции может вернуть либо тот же тип (с новыми данными), либо новый тип (например, переход конечного автомата).Имея это в виду, вы могли бы иметь переход сущности где-то вниз по потоку от рабочего процесса, от ShoppingCart к Order.

Такого рода вещи могут быть выполнены на любом языке, поддерживающем протоколы (Clojure, Swift, Groovy и т. Д.) Или языки, поддерживающие типы объединения (F #, Elm, Haskell, Reason, OCaml).Использование любого из них позволяет одному и тому же сообщению (например, place) вести себя по-разному.Функция place на ShoppingCart проверяет, есть ли у нее все необходимое для перехода на Order.

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

0 голосов
/ 14 февраля 2019

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

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

Как примечание: на самом деле в зависимости от отрасли, над которой вы работаете, пустой порядок имеет смысл. Поговорите со своими экспертами в предметной области, чтобы проверить, не является ли пустой заказ чем-то, что имеет другое имя .Вы можете обнаружить, что пустой ордер является чем-то действительным, со своими собственными правилами и действиями, но у него есть другое имя для него, и у него другой набор инвариантов, и это может значительно упростить вам задачу, если это произойдет: вы могли бы иметь«черновик заказа» как часть вашей модели, который будет органично работать как фабрика по вашему заказу.

...