DDD - изменение дочерних объектов в совокупности

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

Порядок (совокупный корень) создается с помощью нескольких OrderLines (дочерних сущностей). Согласно бизнес-правилам, каждая OrderLine должна поддерживать ту же идентичность для жизни Ордена. OrderLines имеют много свойств (20+) и могут быть мутированы довольно часто до того, как Порядок считается "заблокированным". Кроме того, существуют инварианты, которые должны выполняться на корневом уровне; например, каждая строка заказа имеет количество, а общая величина для ордера не может превышать X.

Я не уверен, как моделировать этот сценарий при рассмотрении изменений в OrderLines. У меня есть 4 варианта, которые я могу себе представить, но ни один из них не кажется удовлетворительным:

1) Когда придет время изменить OrderLine, сделайте это, используя ссылку, предоставленную корнем. Но я теряю способность проверять инвариантную логику в корне.

var orderLine = order.GetOrderLine(id);
orderLine.Quantity = 6;

2) Вызвать метод в порядке. Я могу применить всю инвариантную логику, но затем я застрял в распространении методов для изменения многих свойств OrderLine:

order.UpdateOrderLineQuantity(id, 6);
order.UpdateOrderLineDescription(id, description);
order.UpdateOrderLineProduct(id, product);
...

3) Это может быть проще, если я обработал OrderLine как объект Value, но он должен поддерживать идентичность для бизнес-требований.

4) Я могу получить ссылки на OrderLines для модификаций, которые не влияют на инварианты, и пройти через Order для тех, которые делают. Но что, если на инварианты влияет большинство свойств OrderLine? Это возражение гипотетично, поскольку только некоторые свойства могут влиять на инварианты, но это может измениться, поскольку мы раскрываем больше бизнес-логики.

Любые предложения приветствуются... не стесняйтесь сообщать мне, плотно ли я.

Ответ 1

  • Не является оптимальным, поскольку позволяет разрешить инвариант домена.

  • Это приведет к дублированию кода и ненужному взрыву метода.

  • То же, что и 1). Использование объекта Value не поможет поддерживать инвариант домена.

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

Ответ 2

Одним из недостатков 4 в отличие от 2 является отсутствие согласованности. В некоторых случаях было бы полезно поддерживать определенную степень согласованности в отношении обновления позиций заказа. Не может быть сразу понятно, почему определенные обновления выполняются через заказ, а другие - в позиции заказа. Более того, если строки порядка имеют 20+ свойств, возможно, это знак того, что существует возможность группировки среди этих свойств, что приводит к меньшему количеству свойств в строке заказа. В целом, подход 2 или 4 является прекрасным, если вы убедитесь, что операции выполняются атомарно, согласованно и в соответствии с вездесущим языком.

Ответ 3

Существует пятый способ сделать это. Вы можете запустить событие домена (например, QuantityUpdatedEvent(order, product, amount)). Пусть агрегат обрабатывает его внутренне, просматривая список строк заказа, выбирает тот, который соответствует соответствующему продукту, и обновляет его количество (или делегирует операцию на OrderLine, что еще лучше)

Ответ 4

Событие домена является наиболее надежным решением.

Однако, если этот излишний, вы также можете сделать вариант # 2 с использованием шаблона объекта параметров - иметь единственную функцию ModfiyOrderItem в корне сущности. Отправьте новый, обновленный элемент заказа, а затем внутренне заказ подтверждает этот новый объект и делает обновления.

Итак, ваш типичный рабочий процесс превратился бы в нечто вроде

var orderItemToModify = order.GetOrderItem(id);
orderItemToModify.Quantity = newQuant;

var result = order.ModifyOrderItem(orderItemToModfiy);
if(result == SUCCESS)
{
  //good
 }
else
{
   var reason = result.Message; etc
}

Основной недостаток здесь - это то, что он позволяет программисту изменять элемент, но не совершать его и не понимать. Однако он легко расширяется и проверяется.

Ответ 5

Здесь еще один вариант, если ваш проект небольшой, и вы хотите избежать сложности событий домена. Создайте сервис, который обрабатывает правила для Order и передает его методу на OrderLine:

public void UpdateQuantity(int quantity, IOrderValidator orderValidator)
{
    if(orderValidator.CanUpdateQuantity(this, quantity))
        Quantity = quantity;
}

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

Это может быть хорошим решением, если ваш проект мал и вам не нужна сложность событий домена.

Недостатком этого метода является то, что вы передаете службу проверки для Order to OrderLine, где она действительно не принадлежит. Напротив, повышение события домена перемещает логику заказа из OrderLine. Затем OrderLine может просто сказать миру: "Эй, я меняю свое количество". и логика проверки ордера может иметь место в обработчике.