我一直在DDD中做一个小项目。我到处都看到值对象是不可变的,因此,不能修改。只有实体。
我将用每个人都用的例子。住址假设Address是Customer实体的VO(也是根聚合)。如果用户更新他的地址,这在任何购物车场景中都是有效的,那么我该怎么办?我必须修改那个VO地址,以便将其持久化到数据库中。意思是,这个VO必须有一个身份,以便我在数据库中识别它。除非NHibernate使用映射来处理它,对吧。林的情况并非如此。或者我想我必须创建一个新的聚合,其中Address是一个实体?然后几乎在我需要地址的地方都有一份地址副本?
然而。我仍然无法包装整个实体/VO概念。在我看来,就像所有在数据库中有表示的东西一样,即使你在模型中使用它作为VO,它在某种程度上也是一个实体,因为为了使它持久化,你需要某种KEY来在数据库中识别它。
一天下来,所有值对象的数据都来自一个数据库(大部分)。所以我仍然不明白,在数据更新的情况下,你必须如何使它们不可变。
经过两个月的深入阅读,我发现整个DDD是一个巨大的矛盾问题。阅读所有这些博客,你就会明白我在说什么。不幸的是,没有任何演示应用程序可以作为榜样或指导。它们在很大程度上都受到开发人员偏好的影响。然后他们最终攻击了对方的博客。一夜之间DDD大师的博客真的帮助了整个社区的混乱。
感谢您的光临。期待着进行建设性的讨论。
我认为您的困惑在于数据库行标识和DDD中与实体相关的标识概念之间的人为耦合。它们当然是相关的,因为实体将在数据库中具有作为标识列表示的相应标识。然而,仅仅因为某个对象具有数据库行的标识,并不意味着该对象具有DDD意义上的标识。
在地址示例中,包含地址VO的值通常存储在与客户实体相同的行中。通过这种方式,地址是一个值对象,因为它没有存储在自己的行中,也没有标识。更新地址时,会更改客户实体上地址属性的值,该值反过来会反映在数据库行中。
在某些情况下,值对象会存储在自己的行中。例如,在定型的销售订单模型中,订单是一个实体(聚合根),行项目是价值对象。虽然行项目是VO,但在关系模型中,它们存储在自己的表中,很可能有主键。然而,在域模型中,VO被绑定到订单实体,并且在该实体之外没有身份。
实际上,从Java中的原始DDDSample开始,可能在你想象的每一种技术中都有很多演示。此示例至少有两个到.NET的端口:NDDD和DDDSample.NET.
至于VO和持久性,如果你认为VO是一个对象,它包装了代表实体的表中的几列,那就很容易了(例如,Money VO包装了金额和货币)。当您希望在SQL级别上规范化数据并为VO创建一个表(而不是将它们嵌入实体表中)时,问题就开始了。我不知道这个问题的好解决方案,但幸运的是,在用关系存储实现DDD时,这不是一个好的做法。为什么?最重要的原则之一是,总量应尽可能相互独立。在对象模型中共享一个VO类符合此规则,但在数据存储中共享VO表则不然,因为例如,这个表可能会成为锁定瓶颈。
简而言之:当使用SQL数据库存储域模型时,请考虑不规范聚合之间的模型。
您也可以尝试从现实世界(或领域)的角度来看待这些问题。地址很少更改。更常见的情况是,客户更改了地址(例如,他要搬到另一个公寓),而地址本身也发生了更改。
考虑到这一点,地址必须是一个值对象。
嘿,我知道这是一个很老的问题,但我正在进行DDD查询,也有一些类似的查询。
我找到的DDD解决方案的最佳途径是忘记DB并考虑模型。因此,在上述情况下,如果你有一个客户和一个地址VO,并且你想要一个客户的地址列表,比如在电子商务网站的场景中,客户有多个发货地址可供选择。好吧,在域模型的真实上下文中,这些地址不仅仅是地址VO,它们是CustomerShippingAddress实体。
因此,您的CustomerShippingAddress实体有一个标识,它有一个地址VO。也许它还有其他一些东西,比如IsDefault和DateAdded等
我建议您只能从传入Address VO的Customer实体实例化CustomerShippingAddress,然后返回CustomerShipping Address的聚合根,该根包含对客户的ID引用。
通过这种方式,您可以维护AddressVO,但可以在多个场景中重用该VO。
当客户选择一个地址作为订单的发货地址时,他们选择的AddressVO将应用于订单聚合。这意味着该订单上的发货地址被及时锁定到当时的地址VO。稍后可能会修改CustomerShippingAddress,但订单上的发货地址VO将永远保持不变。