Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/apache-spark/6.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Domain driven design DDD:我真的需要加载聚合中的所有对象吗?(性能问题)_Domain Driven Design - Fatal编程技术网

Domain driven design DDD:我真的需要加载聚合中的所有对象吗?(性能问题)

Domain driven design DDD:我真的需要加载聚合中的所有对象吗?(性能问题),domain-driven-design,Domain Driven Design,在DDD中,存储库加载整个聚合—我们要么加载所有聚合,要么不加载任何聚合。这也意味着应该避免延迟加载 我关心的是性能。如果这会导致将数千个对象加载到内存中呢?例如,Customer的聚合返回一万个订单 在这种情况下,这是否意味着我需要重新设计并重新思考我的聚合?DDD是否就此问题提供建议 在这种情况下,这是否意味着我需要重新设计并重新思考我的聚合 几乎可以肯定 聚合设计的驱动因素不是结构,而是行为。我们不在乎“一个用户有成千上万的订单”。我们关心的是当您试图处理更改时需要检查哪些状态—您需要加载

在DDD中,存储库加载整个聚合—我们要么加载所有聚合,要么不加载任何聚合。这也意味着应该避免延迟加载

我关心的是性能。如果这会导致将数千个对象加载到内存中呢?例如,
Customer
的聚合返回一万个
订单

在这种情况下,这是否意味着我需要重新设计并重新思考我的聚合?DDD是否就此问题提供建议

在这种情况下,这是否意味着我需要重新设计并重新思考我的聚合

几乎可以肯定

聚合设计的驱动因素不是结构,而是行为。我们不在乎“一个用户有成千上万的订单”。我们关心的是当您试图处理更改时需要检查哪些状态—您需要加载哪些数据才能知道更改是否有效

通常,您会意识到更改订单并不(或不应该)依赖于系统中其他订单的状态,这很好地表明两个不同的订单不应该是同一个聚合的一部分。

看看Vernon的这一系列三篇文章。我发现它们对于理解何时以及如何设计较小的聚合而不是大型集群聚合非常有用

编辑

我想举几个例子来改进我之前的答案,请随意分享你对它们的想法

首先,关于聚合的快速定义(摘自Scott Millet的《领域驱动设计的模式、原则和实践》一书)

实体和值对象协作形成满足域模型内不变量的复杂关系。在处理对象的大型互连关联时,在对域对象执行操作时,通常很难确保一致性和并发性。域驱动设计具有聚合模式,以确保一致性并为对象图定义事务并发边界。大型模型被不变量分割并分组为实体和值对象的集合,这些实体和值对象被视为概念整体

让我们通过一个例子来了解实际中的定义

简单示例

第一个示例显示了在对域对象执行操作时,定义聚合根如何帮助确保一致性

考虑到下一个业务规则:

中标的拍卖出价必须始终在拍卖结束前进行。如果在拍卖结束后进行中标,则域处于无效状态,因为不变量已被破坏,并且模型未能正确应用域规则

这里有一个由拍卖和出价组成的聚合,其中拍卖是聚合根

如果我们说Bid也是一个单独的聚合根,那么您将拥有一个
BidsRepository
,您可以很容易地做到:

var newBid = new Bid(money);
BidsRepository->save(auctionId, newBid);
您在保存出价时未通过定义的业务规则。但是,如果将拍卖作为唯一的聚合根,您将强制执行设计,因为您需要执行以下操作:

var newBid = new Bid(money);
auction.placeBid(newBid);
auctionRepository.save(auction);
因此,您可以在方法
placeBid
中检查您的不变量,如果想要进行新的出价,任何人都不能跳过它

这里很清楚,出价的状态取决于拍卖的状态

复杂示例

回到您的订单与客户关联的示例,似乎没有不变量使我们定义一个由客户及其所有订单组成的庞大集合,我们可以通过标识符引用来保持两个实体之间的关系。通过这样做,我们避免了在获取客户时加载所有订单,并减轻了并发问题

但是,假设现在业务定义了下一个不变量:

我们想为顾客提供一个口袋,这样他们就可以花钱购买产品了。因此,如果客户现在想购买产品,就需要有足够的钱来购买

这样说,pocket是客户聚合根中的一个VO。现在看来,拥有两个分离的聚合根(一个用于客户,另一个用于订单)并不是满足新不变量的最佳方法,因为我们可以在不检查规则的情况下保存新订单。看来我们不得不把客户当作根本。这将影响我们的性能、可伸缩性和并发性等问题

解决方案?最终的一致性。如果我们允许客户购买产品怎么办?也就是说,拥有订单的聚合根,因此我们创建订单并保存它:

var newOrder = new Order(customerId, ...);
orderRepository.save(newOrder);
我们在创建订单时发布一个事件,然后异步检查客户是否有足够的资金:

class OrderWasCreatedListener:
    var customer = customerRepository.findOfId(event.customerId);
    var order = orderRepository.findOfId(event.orderId);
    customer.placeOrder(order); //Check business rules
    customerRepository.save(customer);
如果一切都很好,我们已经满足了不变量的要求,同时保持了我们在开始时想要的设计,每个请求只修改一个聚合根。否则,我们将向客户发送电子邮件,告知她资金不足的问题。我们可以通过在电子邮件中添加她可以用当前预算购买的备选方案,并鼓励她自掏腰包来提前购买

考虑到用户界面可以帮助我们避免客户在没有足够资金的情况下付款,但我们不能盲目信任用户界面


希望您发现这两个示例都很有用,并且如果您为公开的场景找到更好的解决方案,请告诉我:-)

如果您的
Customer
aggregate包含一万个
Order
对象,则很可能
Order
本身就是一个聚合。总结了聚合的主要设计驱动因素。您也可以重新思考您的有界上下文。据我所知,基本规则是,对于读取操作,您不需要加载整个聚合。对于写操作,由于事务/i,您应该加载整个聚合