我在处理相当复杂的场景时遇到了一些困难。我见过很多类似的问题,但没有一个能让我满意。
使用多个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属性的影响呢?这个异议是假设的,因为只有少数属性可以影响不变量,但是当我们发现更多业务逻辑时,这可能会发生变化。
任何建议都表示赞赏......如果我是密集的话,请随时告诉我。
答案 0 :(得分:5)
不是最佳的,因为它允许破坏域不变。
会导致代码重复和不必要的方法爆炸。
与1相同。使用Value Object无助于维护域不变。
我会选择这个选项。在实现之前,我也不会担心潜在和假设的变化。设计将随着您对域的理解而发展,并且可以在以后进行重构。对于未来可能不会发生的变化,阻碍现有设计的确没有任何价值。
答案 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
验证)。