DDD: 值对象内部可以包含列表吗?

10

我对领域驱动设计并不熟悉,最近开始为项目创建一个领域模型。目前还没有决定使用什么 ORM(尽管我可能会选择 NHibernate),我正在努力确保我的值对象只是值对象。

我有一些几乎没有行为的值对象,除了封装“类似”的术语外:

public class Referral {
    public Case Case { get; set; } // this is the a reference to the aggregate root
    public ReferralType ReferralType { get; set; } // this is an enum
    public string ReferralTypeOther { get; set; }
} // etc, etc.

这个特定的类引用了两级上层的“Case”,所以如果我要访问“Referral”,我可以这样写代码: case.social.referral(Case,Social和Referral都是类,一个Case里面只有一个Social,而一个Social里面只有一个Referral)。现在我打字的时候看一下,我不认为我需要在“Referral”里加入一个“Case”,因为我可以通过“Social”实体访问它,对吗?

现在,在我的脑海中,毫无疑问这应该是一个值对象(VO),我计划使用的方法是要么让NHibernate给它分配一个代理标识符(我仍然不太清楚,如果有人能详细解释一下,那会帮助我,因为我不知道代理标识符是否需要我已经有了VO中的Id,或者它是否可以在没有ID的情况下运行),要么是一个受保护的Id属性,该属性不会在“Referral”类外部公开(仅用于持久化到数据库)。

现在,回到我的标题问题:VO内部应该有集合吗?(在我的情况下是一个列表)?我只能把它看作是数据库中的一对多关系,但由于没有身份识别,似乎不能将其作为实体类。以下是代码:

public class LivingSituation {
    private IList<AdultAtHome> AdultsAtHome { get; set; }
    public ResidingWith CurrentlyResidingWith { get; set } // this is an enum
} // etc, etc.

这个类目前没有ID,而AdultsAtHome类只有内在类型(字符串、整数)。因此我不确定它是否应该成为实体,或者是否可以保持为值对象,我只需要配置我的ORM,使用它们自己的表和一个私有/受保护的ID字段来使用1:m关系,以便ORM可以将其持久化到数据库。

另外,我应该为我的每个类选择规范化的表,还是不用?我认为只有当有可能将多个类的实例分配给实体或值对象时,并/或者有可能与其中一些对象具有集合1:m关系时,我才需要为每个类使用一个表。对于一些具有内在类型的值对象,我没有问题使用单个表,但对于嵌套类型,我认为使用规范化的表会更有优势。对此有什么建议吗?

对于这些问题我提出了许多疑问:

1)对于我的值对象,我需要一个替代标识符(例如NHibernate)吗?

2)如果答案是肯定的,那么这个标识符是否需要是私有/受保护的,以便我的值对象在概念上“保持”为值对象?

3)值对象可以包含其他值对象(例如,列表中的值对象),还是这构成实体?(我认为答案是否定的,但在进一步进行之前最好确认一下。)

4)从聚合根中向下几级的值对象需要对聚合根进行引用吗?(我认为不需要,这可能是我在编写模型时疏忽了,有人同意吗?)

5)当使用ORM将更简单的值对象映射到属于我的实体的同一个表时,可以针对某些内容(例如嵌套类型和/或具有集合属性的类型,这些属性需要它们自己的表以进行1:m关系)使用规范化的表吗?

谢谢您。


1
你最迫切的问题是什么?也许你应该先解决那个问题。 - Robert Harvey
1
领域驱动设计并不是一种开发技术,它是为您和客户提供一个共同语言的方式。 - Robert Harvey
我猜问题1和2与NHibernate将对象持久化到数据库的方式有关,我可以在他们的网站上查找。但是问题3和4是我需要澄清的问题,因为我想尽可能地遵循这些模式,但我不确定这些问题。问题5也涉及NHibernate和什么被认为是“最佳实践”。它们都很紧迫,因为它们都相关。 - Miguel G
1个回答

13

看看这里相关问题的答案:这里这里


1)是的 - 如果你将VO存储在它们自己的表中

2)如果您可以使用私有/受保护的ID属性,那么很好。 或者,您可以使用显式接口来“隐藏”ID属性。

但是,仔细阅读您的问题,您是否在暗示查看ID属性的开发人员会自动假定该对象是实体? 如果是这样,他们需要(重新)培训。

3)是的,它可以,但有以下限制:

  • 应该相当罕见
  • 它只应引用其他VO

此外,请考虑:VO不应该持久存在。每次需要它时重新创建整个VO是否容易/高效?如果不是,请将其设置为Entity。

4)取决于您想要如何实现聚合锁定。如果您想使用Ayende的解决方案,答案是是。否则,您需要一种机制来遍历对象图回到聚合根。

5)可以。不要忘记DDD是持久性无关的(在理想世界中!)。


然而...

我认为Referral应该是一个Entity。 想象一下这些对话:

对话1:

  • 汤姆:“嘿,乔!你能给我大卫·琼斯的推荐吗?”
  • Joe:“哪一个?”
  • Tom:“抱歉,我指的是推荐编号123”
  • 对话2:

    • Tom:“嗨Joe!你可以给我David Jones的引荐吗?”
    • Joe:“哪一个?”
    • Tom:“我不在乎-随便给我一个。”

    对话1表明Referral是一个实体,而对话2表明它是一个值对象。

    还有一件事:Referral.ReferralType在其生命周期中是否会更改(另一个提示表明它应该是一个实体)?如果不会更改,请考虑使用多态性并让NH处理它。

    希望这可以帮助!


谢谢,我已经看过了那些内容,我同意它们回答了问题1,我猜问题2是一个实现细节,我可以通过阅读NHibernate的文档来了解。我希望对问题3、4和5有一些澄清(尽管如我之前的评论所述),我认为问题5更像是一种“最佳实践”方法 - 我应该这样做还是有更好的方法? - Miguel G
ReferralType 在创建后不会更改,但您的观点是有道理的,给它一个 Id 是有意义的。谢谢! - Miguel G
不过,我在考虑创建一个IValueObject接口,并为其添加一个Id属性,只有那些将拥有自己的表的VO才会从中派生并显式实现它。我会认真思考Referral和其他一些最初是值对象但确实需要一种身份类型的类型,因为其中的信息将是特定的,并且应该被排序(以跟踪进度等),所以它们应该是实体。我还会阅读Ayende关于聚合锁定的帖子。再次感谢! - Miguel G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接