Domain driven design DDD-聚合中子对象的修改

Domain driven design DDD-聚合中子对象的修改,domain-driven-design,aggregateroot,Domain Driven Design,Aggregateroot,我很难找到处理相当复杂情况的最佳方法。我见过很多类似的问题,但没有一个能让我满意地解决这个问题 使用多个订单行(子实体)创建订单(聚合根)。根据业务规则,每个订单行必须在订单的生命周期内保持相同的标识。订单行具有许多(20+)属性,在订单被视为“锁定”之前,可以相当频繁地进行变异。此外,还有一些不变量必须在根级别强制执行;例如,每个订单行都有一个数量,订单的总数量不能超过X 我不确定在考虑对订单行进行更改时如何对该场景建模。我有4个选择,我可以想象,但没有一个是令人满意的: 1) 当需要修改订单

我很难找到处理相当复杂情况的最佳方法。我见过很多类似的问题,但没有一个能让我满意地解决这个问题

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

我不确定在考虑对订单行进行更改时如何对该场景建模。我有4个选择,我可以想象,但没有一个是令人满意的:

1) 当需要修改订单行时,请使用root提供的引用进行修改。但是我失去了在根中检查不变逻辑的能力

var orderLine = order.GetOrderLine(id);
orderLine.Quantity = 6;
2) 调用订单上的方法。我可以应用所有不变的逻辑,但随后我不得不使用大量的方法来修改订单行的许多属性:

order.UpdateOrderLineQuantity(id, 6);
order.UpdateOrderLineDescription(id, description);
order.UpdateOrderLineProduct(id, product);
...
public void UpdateQuantity(int quantity, IOrderValidator orderValidator)
{
    if(orderValidator.CanUpdateQuantity(this, quantity))
        Quantity = quantity;
}
3) 如果我将OrderLine视为一个值对象,这可能会更容易,但它必须根据业务需求维护相同的标识

4) 我可以为不影响不变量的修改获取对订单行的引用,并为那些影响不变量的修改遍历订单。但如果不变量受大多数订单行属性的影响呢?这个异议是假设性的,因为只有少数属性可以影响不变量,但随着我们发现更多的业务逻辑,这些属性会发生变化

任何建议都非常感谢…如果我很紧张,请随时告诉我

  • 不是最优的,因为它允许断开域不变量

  • 将导致代码重复和不必要的方法爆炸

  • 与1)相同。使用值对象无助于保持域不变

  • 我会选择这个选项。我也不会担心潜在的和假设的变化,直到它们实现。设计将随着您对该领域的理解而发展,并且可以在以后进行重构。为了将来可能不会发生的一些更改而阻碍现有的设计,实际上没有任何价值


  • 与2相比,4的一个缺点是缺乏一致性。在某些情况下,在更新订单行项目方面保持一定程度的一致性可能是有益的。某些更新是通过订单完成的,而其他更新是通过订单行项目完成的,这一点可能还不清楚。此外,如果订单行具有20+个属性,这可能表明这些属性之间存在分组的可能性,从而导致订单行上的属性更少。总的来说,只要确保操作原子化、一致性并与通用语言保持一致,方法2或4就可以了。

    有第五种方法可以做到这一点。您可以启动(例如,
    QuantityUpdateEvent(订单、产品、金额)
    )。通过查看订单行列表,让聚合在内部处理它,选择具有匹配产品的订单行,并更新其数量(或者将操作委托给
    订单行
    ,这更好)

    域事件是最健壮的解决方案

    但是,如果这太过分了,您也可以使用参数对象模式执行#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
    }
    

    这里的主要缺点是它允许程序员修改项目,但不提交它,也不实现它。但是,它很容易扩展和测试。

    如果您的项目很小,并且希望避免域事件的复杂性,那么这里有另一个选项。创建一个处理订单规则的服务,并将其传递给OrderLine上的方法:

    order.UpdateOrderLineQuantity(id, 6);
    order.UpdateOrderLineDescription(id, description);
    order.UpdateOrderLineProduct(id, product);
    ...
    
    public void UpdateQuantity(int quantity, IOrderValidator orderValidator)
    {
        if(orderValidator.CanUpdateQuantity(this, quantity))
            Quantity = quantity;
    }
    
    CanUpdateQuantity将当前订单行和新数量作为参数。它应该查找订单,并确定更新是否导致订单总量发生冲突。(您必须确定如何处理更新冲突。)

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

    这种技术的一个缺点是,您正在将订单的验证服务传递到OrderLine,而OrderLine实际上不属于该服务。相反,引发域事件会将订单逻辑移出订单行。然后,订单行就可以对世界说,“嘿,我在改变我的数量。”订单验证逻辑可以在处理程序中进行。

    使用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;
        }  
    }
    
    public类OrderLineDto
    {
    公共整数数量{get;set;}
    公共字符串说明{get;set;}
    public int ProductId{get;set;}
    }
    公共阶级秩序
    {
    公共int?Id{get;private set;}
    公共IList命令行{get;private set;}
    public void UpdateOrderLine(int-id,OrderLineDto值)
    {
    var orderLine=订单行
    .其中(x=>x.Id==Id)
    .FirstOrDefault();
    if(orderLine==null)
    {
    抛出新的InvalidOperationException(“未找到医嘱行”);
    }
    //这里有一些域验证
    //抛出新的InvalidOperationException(“订单行更新无效”);
    orderLine.Quantity=value.Quantity;
    orderLine.Description=values.Description;
    orderLine.ProductId=values.ProductId;
    }  
    }
    
    • 这里唯一的问题是,
      OrderLines
      属性具有公共getter,此类的用户可以将项添加到集合中。我能想到的唯一方法是隐藏getter并添加新的getter,它将返回col