DDD - 聚合中子对象的修改

时间:2012-05-23 21:32:45

标签: domain-driven-design aggregateroot

我在处理相当复杂的场景时遇到了一些困难。我见过很多类似的问题,但没有一个能让我满意。

使用多个OrderLines(子实体)创建订单(聚合根)。根据业务规则,每个OrderLine必须在订单的生命周期内保持相同的标识。 OrderLines具有许多(20+)属性,并且在Order被视为“锁定”之前可以经常变异。此外,还有一些不变量必须在根级别强制执行;例如,每个订单行都有一个数量,订单的总数量不能超过X.

在考虑更改OrderLines时,我不确定如何对此方案进行建模。我有4个选择可以设想,但似乎没有一个令人满意:

1)当需要修改OrderLine时,请使用root提供的引用来完成。但我失去了检查根中不变逻辑的能力。

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

2)在订单上调用方法。我可以应用所有不变的逻辑,但后来我坚持使用大量的方法来修改OrderLine的许多属性:

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

3)如果我将OrderLine视为价值对象,这可能会更容易,但它必须按业务要求保持相同的身份。

4)我可以获取对OrderLines的引用,以获取不影响不变量的修改,并通过Order查看那些有效的修改。但是如果不变量受大多数OrderLine属性的影响呢?这个异议是假设的,因为只有少数属性可以影响不变量,但是当我们发现更多业务逻辑时,这可能会发生变化。

任何建议都表示赞赏......如果我是密集的话,请随时告诉我。

6 个答案:

答案 0 :(得分:5)

  1. 不是最佳的,因为它允许破坏域不变。

  2. 会导致代码重复和不必要的方法爆炸。

  3. 与1相同。使用Value Object无助于维护域不变。

  4. 我会选择这个选项。在实现之前,我也不会担心潜在和假设的变化。设计将随着您对域的理解而发展,并且可以在以后进行重构。对于未来可能不会发生的变化,阻碍现有设计的确没有任何价值。

答案 1 :(得分:5)

4与2相反的一个缺点是缺乏一致性。在某些情况下,在更新订单行项目方面保持一定程度的一致性可能是有益的。可能不会立即清楚为什么通过订单完成某些更新,而其他更新通过订单行项目完成。此外,如果订单行具有20多个属性,则可能表示可能会在这些属性之间进行分组,从而导致订单行上的属性更少。总的来说,方法2或4是好的,只要你确保操作保持原子性,一致性并与普遍存在的语言保持一致。

答案 2 :(得分:5)

有第五种方法可以做到这一点。您可以点domain event(例如QuantityUpdatedEvent(order, product, amount))。让聚合通过遍历订单列表在内部处理它,选择具有匹配产品的产品并更新其数量(或将操作委托给OrderLine,这样更好)

答案 3 :(得分: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
}

这里的主要缺点是它允许程序员修改项目,但不提交它而没有意识到。但它很容易扩展和测试。

答案 4 :(得分:1)

如果您的项目很小并且您想避免域事件的复杂性,这是另一个选项。创建一个处理Order规则的服务,并将其传递给OrderLine上的方法:

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

CanUpdateQuantity将当前OrderLine和新数量作为参数。它应查找订单并确定更新是否导致违反总订单数量。 (您必须确定如何处理更新违规。)

如果您的项目很小并且您不需要域事件的复杂性,这可能是一个很好的解决方案。

这种技术的缺点是你将Order的验证服务传递给OrderLine,它实际上并不属于它。相比之下,提升域事件会使Order逻辑移出OrderLine。然后,OrderLine可以向全世界说:“嘿,我正在改变我的数量。”订单验证逻辑可以在处理程序中进行。

答案 5 :(得分:0)

使用DTO有什么用?

public class OrderLineDto
{
    public int Quantity { get; set; }
    public string Description { get; set; }
    public int ProductId { get; set; }
}

public class Order
{
    public int? Id { get; private set; }
    public IList<OrderLine> OrderLines { get; private set; }

    public void UpdateOrderLine(int id, OrderLineDto values)
    {
        var orderLine = OrderLines
            .Where(x => x.Id == id)
            .FirstOrDefault();

        if (orderLine == null)
        {
            throw new InvalidOperationException("OrderLine not found.");
        }

        // Some domain validation here
        // throw new InvalidOperationException("OrderLine updation is not valid.");

        orderLine.Quantity = values.Quantity;
        orderLine.Description = values.Description;
        orderLine.ProductId = values.ProductId;
    }  
}
  • 这里唯一的问题是OrderLines属性具有公共获取者,并且此类的用户可以将项目添加到集合中。我能想到的唯一预防方法是隐藏吸气剂并添加新的吸气剂,如果需要,它将返回DTO的集合。
  • UpdateOrderLine方法的id参数可以成为DTO的一部分,与此相比可能会更好地工作
  • 可能您可以直接接受OrderLine作为参数而不是OrderLineDto(如果要在传递给OrderLine之前使用某些Order验证)。