DDD - Modifications of child objects within aggregate

本秂侑毒 提交于 2020-01-22 09:30:34

问题


I am having some difficulty working out the best way to handle a fairly complex scenario. I've seen quite a few similar questions, but none addressed this scenario to my satisfaction.

An Order (aggregate root) is created with multiple OrderLines (child entities). According to business rules, each OrderLine must maintain the same identity for the life of the Order. OrderLines have many (20+) properties and can be mutated fairly often before the Order is considered "locked". In addition, there are invariants that must be enforced at the root level; for example, each Order Line has a quantity and the total quantity for the Order cannot exceed X.

I'm not sure how to model this scenario when considering changes to OrderLines. I have 4 choices that I can conceive of, but none seem satisfactory:

1) When the time comes to modify an OrderLine, do it using a reference provided by the root. But I lose the ability to check invariant logic in the root.

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

2) Call a method on the order. I can apply all invariant logic, but then I'm stuck with a proliferation of methods to modify the many properties of the OrderLine:

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

3) This might be easier if I treated the OrderLine as a Value Object, but it has to maintain the same identity per business requirements.

4) I can grab references to the OrderLines for modifications that do not affect invariants, and go through the Order for those that do. But then what if invariants are affected by most of the OrderLine properties? This objection is hypothetical, since only a few properties can affect invariants, but thhat can change as we uncover more business logic.

Any suggestions are appreciated...do not hesitate to let me know if I'm being dense.


回答1:


  1. Is not optimal because it allows breaking domain invariant.

  2. Will result in code duplication and unnecessary method explosion.

  3. Is the same as 1). Using Value Object will not help with maintaining domain invariant.

  4. This option is what I would go with. I would also not worry about potential and hypothetical changes until they materialize. The design will evolve with your understanding of the domain and can always be refactor later. There is really no value in hindering your existing design for the sake of some future changes that may not happen.




回答2:


One drawback of 4 as opposed to 2 is lack of consistency. In certain instances it might be beneficial to maintain a degree of consistency in regards to updating order line items. It may not be immediately clear why certain updates are done through the order while others through the order line item. Furthermore, if order lines have 20+ properties, perhaps that is a sign that there is potential for grouping among those properties resulting in fewer properties on the order line. Overall, approach 2 or 4 is fine as long as you make sure to keep operations atomic, consistent and in correspondence with ubiquitous language.




回答3:


There is a fifth way of doing this. You can fire a domain event (e.g. QuantityUpdatedEvent(order, product, amount)). Let the aggregate handle it internally by going through the list of orderlines, select the one with matching product and update its quantity (or delegate the operation to OrderLine which is even better)




回答4:


The domain event is the most robust solution.

However if that's overkill you can also do a variation of #2 using the Parameter Object pattern - have a single ModfiyOrderItem function on the entity root. Submit a new, updated order item, and then internally the Order validates this new object and makes the updates.

So your typical workflow would turn into something like

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

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

The main shortcoming here is it allows a programmer to modify the item, but not commit it and not realize that. However it is easily extensible and testable.




回答5:


Here's another option if your project is small and you want to avoid the complexity of domain events. Create a service that handles the rules for Order and pass it in to the method on OrderLine:

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

CanUpdateQuantity takes the current OrderLine and the new quantity as arguments. It should lookup the Order and determine if the update causes a violation in the total Order quantity. (You will have to determine how you want to handle an update violation.)

This may be a good solution if your project is small and you don't need the complexity of domain events.

A downside to this technique is you are passing a validation service for Order into OrderLine, where it really doesn't belong. By contrast, raising a domain event moves the Order logic out of OrderLine. The OrderLine can then just say to the world, "Hey, I'm changing my quantity." and the Order validation logic can take place in a handler.



来源:https://stackoverflow.com/questions/10728187/ddd-modifications-of-child-objects-within-aggregate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!