15

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

使用多个 OrderLine(子实体)创建一个 Order(聚合根)。根据业务规则,每个 OrderLine 必须在 Order 的生命周期内保持相同的身份。OrderLines 有许多 (20+) 属性,并且在 Order 被视为“锁定”之前可以经常发生突变。此外,还有一些必须在根级别强制执行的不变量;例如,每个订单行都有一个数量,订单的总数量不能超过 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 视为值对象,这可能会更容易,但它必须根据业务需求保持相同的身份。

4) 对于不影响不变量的修改,我可以获取对 OrderLines 的引用,并为那些影响不变量的修改执行 Order。但是,如果不变量受大多数 OrderLine 属性的影响怎么办?这个反对是假设的,因为只有少数属性可以影响不变量,但是随着我们发现更多的业务逻辑,这可能会改变。

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

4

6 回答 6

7
  1. 不是最优的,因为它允许打破域不变量。

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

  3. 与 1) 相同。使用值对象将无助于维护域不变性。

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

于 2012-05-23T23:17:03.987 回答
7

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

于 2012-05-24T02:10:54.170 回答
5

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

于 2012-07-31T10:22:13.813 回答
5

领域事件是最稳健的解决方案。

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

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

于 2013-02-14T17:47:48.370 回答
2

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

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

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

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

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

于 2015-02-26T21:41:17.397 回答
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属性具有公共 getter,并且此类的用户可以将项目添加到集合中。我能想到如何防止它的唯一方法是隐藏 getter 并添加新的 getter,如果需要,它将返回 DTO 的集合。
  • 方法的 id 参数UpdateOrderLine可以是 DTO 的一部分,使用它可能会更好
  • 可能您可以OrderLine直接接受作为参数而不是OrderLineDto(如果您想OrderLine在传递给之前使用一些验证Order)。
于 2020-02-08T22:28:36.283 回答