DDD 上的所有材料都将其指定为严格的不,但我最近遇到了一个场景,它为以其他方式思考提供了令人信服的理由。想象一下 2 个聚合根模板和文档,Template --> (1:n) TemplateParam, Document --> (1:n) ParamValue
最后 2 个根有一个引用Document --> (n:1) Template
。
给定聚合根约束ParamValue
不应该保留对 的引用TemplateParam
,只有它可以通过Template
聚合根获得的瞬态引用来引用它。现在,如果我想强制执行一个规则,例如“文档的每个 ParamValue 应该引用一个有效的 TemplateParam,该 TemplateParam 属于其拥有的文档所引用的模板”。理想情况下,在数据库级别,我会让 ParamValue 对 TemplateValue 进行 FK,如何在 DDD 范例中做到这一点?
2 回答
聚合根的存在是有原因的。它们充当一组相关实体的单个入口点,以强制执行它们的不变量。他们确保没有外部对象可以与这些实体混淆并可能违反它们的不变量。
但是,在您的特定场景中,即使 ParamValue 直接引用 TemplateParam,TemplateParam 也不存在被 Document 聚合中的实体修改的风险。与给定文档的参数关联的值将被修改,但不是每个参数。
为了确保是这种情况,您可以使 TemplateParam 成为不可变的值对象:
(在 C# 中)
public class TemplateParam
{
private readonly string name;
public TemplateParam(string name)
{
this.name = name;
}
public string Name
{
get { return name; }
}
}
因此,您可以将 TemplateParam 封装在 ParamValue 中,而不会因为 TemplateParam 的“外部化”而破坏模板聚合的不变量之一。
从技术上讲,这可能违反了 DDD 的聚合根约束,但我不认为它在精神上是一个,只要您保持“外部化”实体不可变并且不修改它最初所属的对象图。
您可以解决此问题的一种方法是让Template
实体具有用于创建Document
实例的工厂方法,该方法可以强制执行所有ParamValue
实例都与适当的关联的约束TemplateParam
。如果文档是不可变的,那么你就完成了。否则,您可以通过其关联模板将更新应用到文档。此模板可以直接从文档中引用,也可以使用 ID 引用,在这种情况下,封装应用程序服务将在操作需要时检索它。AR 之间的直接引用并不严格违反 DDD,事实上蓝皮书规定这是唯一可以被外部 AR 引用的东西。由于一致性、性能、ORM 映射等其他考虑因素,它最近已成为一个约束。看看本系列文章对有效的聚合设计有所启发。