DDD聚合:将实体保存到另一个聚合中的非根实体的实体

时间:2018-03-09 20:08:42

标签: entity domain-driven-design aggregate

我正在尝试了解实体和聚合之间关系的最佳实践。

想象一下,您拥有产品聚合的设计,由两个实体组成:

汇总根:产品

儿童实体:Sku

产品可以有多个Skus。 Skus和Product的部件号和名称是不变的,因为更改名称必须在事务上确保另一个更新。同样,产品聚合需要确保永远不会重复Skus。

我们还有另一个聚合:StorageLocation 。存储1个或多个Skus的地方。但是,StorageLocation必须知道它存储的特定Sku。即。加拿大的StorageLocation应该存储该国家的Sku,而不是用于美国市场的Sku。

这意味着StorageLocation需要保留对Sku的引用(作为对Product Aggregate Root的引用本身并不能提供足够的信息来确定存储的Sku)。

从我的阅读中,这似乎打破了另一个聚合不应该持有对另一个聚合中的非根实体的引用的原则。所以问题:

  • 只保留Product的标识符,StorageLocation聚合中的Sku是否可以接受?
  • 我知道瞬态引用被认为是允许的,但在这种情况下(至少据我所知),需要存储这些信息。如上所述,存储对Product(或ProductId)的引用是不够的。
  • 产品和Skus具有自然标识符(部件号,Sku编号)。这是否为在StorageLocation聚合中存储这些值提供了更大的灵活性,因为它们具有超出技术含义的含义。
  • 我是以错误的方式接近这个,需要以不同的方式看待事物。我常常发现很难摆脱PK / FK的心态。

由于

2 个答案:

答案 0 :(得分:5)

不存储对儿童实体的引用的指导建立在良好的校长之上,但我认为经常引起混淆。实际上,目标是儿童实体不一定具有全球唯一的特征。标识符,并且没有存储库使用这样的全局唯一标识符直接访问子实体。

但是,如果您的StorageLocation拥有Product的全局唯一标识符,以及SKU可能的本地唯一标识符,那么模式没有任何问题,例如:

var storageLocation = _storageLocationRepository.Get(id);
var product = _productRepository.Get(storageLocation.ProductId);
product.DoSomethingToSku(storageLocation.SkuId);

关键是确保始终从存储库中获取产品,然后通过产品与子实体进行交互,确保产品有机会保护产品。 ; s自己的不变量。

总结一下:

  • 只要您还存储其聚合根的全局标识符,只需将标识符存储到子实体即可。
    • 在这种情况下,子实体ID可以是本地唯一的或全局唯一的 - 只要对子实体的访问是通过聚合根,它就无关紧要。
  • 永远不要直接从存储库加载子实体并在其上调用方法 - 因为聚合根没有机会保护其不变量。
    • 事实上,您甚至不应该拥有子实体的存储库

答案 1 :(得分:2)

  

只保留产品的标识符和StorageLocation聚合中的Sku可接受吗?

是的,因为标识符是DDD中的值对象,所以将标识符存储在StorageLocation聚合中不会破坏持有对另一个聚合的子实体的引用的规则,因为值对象只是一个值对象而不再是与原始聚合有任何直接关联。

  

我知道瞬态参考被认为是允许的,但在这种情况下(至少据我所知),需要存储这些信息。如上所述,存储对Product(或ProductId)的引用是不够的。

StorageLocation聚合根对数据库的脱水应包括需要保留的所有内容。基础结构层确定域对象在物理存储库中的存储方式,并且可能是与域模型完全不同的模型设计,具体取决于持久性技术的关注点。

  

产品和Skus具有自然标识符(部件号,Sku编号)。这是否为在StorageLocation聚合中存储这些值提供了更大的灵活性,因为它们具有超出技术含义的含义。

没有什么比较可以提供更大的灵活性"因为您只使用自然标识符或瞬态引用来从StorageLocation聚合引用Product聚合中的实体。

  

我是以错误的方式接近这个,需要以不同的方式看待事物。我经常发现很难摆脱PK / FK的心态。

请记住,持久层关注点不应与域模型纠缠在一起,其目的是使域模型清晰明确并与当前业务规则同义。