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