如何在DDD中建模聚合并持久化到数据库

本文关键字:合并 持久化 数据库 建模 DDD | 更新日期: 2023-09-27 18:06:35

我只是想摆脱典型的存储库/服务/表示的n层架构的舒适区,并开始研究DDD与聚合,我不得不承认我有点困惑,希望有人能澄清下面的例子:

如果我有一个名为News, NewsImage和Customer的实体,它们都是EF持久化对象,如下所示:

public class Customer
{
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
}
public class NewsImage
{
    public virtual int Id { get; set; }
    public virtual byte[] Data { get; set; }
    public virtual News News { get; set; }
}
public class News
{ 
    public virtual int Id { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<NewsImage> NewsImages { get; set; }
    public virtual Customer Customer { get; set; }
}

据我所知,这些可能是我们将用于将域对象持久化到数据库的对象,但如果我们使用来自域模型的聚合,我们可以有这样的东西:

public class NewsAggregate
{
    public int Id { get; set; }
    public string Name { get; set }
    public void AddImageToNews(byte[] imageData)
    {
         // Hide NewsImage or that object and add the byte[] data here?
    }
}

我的问题如下,我希望得到澄清,因为我确定我误解了这里的基本原则:

  1. 只有聚合对象应该暴露给表示层(或任何消费层)。
  2. 我如何处理转换/持久化聚合对象到数据库,我可以使用映射,这很好,但我怎么知道我是否正在创建一个对象或更新(如果Id设置是否为瞬态?)我如何知道是否添加了新图像以及要更新或删除哪些图像?我认为我遇到的问题是我调用创建传递新闻聚合到存储库并创建它,然后我可以通过EF实体从填充的域获得聚合,然后添加图像,当我传递新闻聚合回来时,我怎么知道为了创建/更新数据发生了什么变化?
    1. 客户应该去哪里,它应该在新闻聚合对象作为一个AddCustomer方法,应该有一个CustomerAggregate有一个AddNews方法,并与这两个选项如何持久?

非常感谢您的任何见解,我一直在阅读并查看演示概念的示例项目,但似乎没有完全解释实现这一目标的最佳方法。

如何在DDD中建模聚合并持久化到数据库

首先:DDD不建议您使用任何特定的体系结构。我已经使用了许多不同的DDD体系结构,您应该使用适合任务的体系结构。显然,如果您以数据驱动的方式思考,您将遇到DDD的许多问题。

DDD是一种用于处理复杂业务规则的方法。如果你的应用价值在于技术资产(如在云端,暴露web服务或一些不错的html5/移动UI),而在于它所处理的业务的复杂性,你就不应该使用它。对于简单的业务规则,不应该使用DDD。经验法则是:如果你不需要领域专家来理解业务,那么你根本就不需要DDD。

然后,为了正确理解聚合,你应该阅读弗农关于这个主题的文章。那篇文章解释了聚合的存在是为了确保业务不变性。你不应该只使用来优化数据库访问

1)这取决于什么容量。有一条规则规定聚合只能直接引用其他聚合,而不能引用其他聚合中包含的实体或值对象。这是为了强制聚合作为一致性边界——它们完全封装了它们"聚合"的内容。每个聚合应该有一个存储库。表示层和任何外层都可能需要以两种通用的方式引用聚合——用于显示目的或用于行为目的。聚合本身不应该太关心它将如何显示,因为查询可以使用更适合该任务的不同模型来实现——读取模型。相反,聚合应该关注行为。是的,在表示层希望在聚合上执行行为的情况下,它应该通过其标识引用聚合。更好的做法是,创建一个应用程序服务来封装域层,并将行为公开为一个简单的facade。

此外,聚合不是单个类,而通常是围绕一个聚合根(一个实体)聚集的一组类。您不一定需要一个单独的类来表示聚合,它可以只是根实体。

2)对于持久性,似乎您正在使用EF,它应该为您处理所有更改跟踪。它应该跟踪哪些对象是持久的,哪些对象是暂时的。像NHibernate这样的orm也会这样做。

2.1)这取决于Customer本身是否是聚合体。如果是,那么News应该只通过ID引用Customer。此外,新闻实体可能需要客户,在这种情况下,应该将客户ID传递给新闻实体的构造函数。如果不需要,则存在将客户与新闻实体关联起来的行为。从领域的角度考虑这个问题——将客户与新闻实体关联起来的意义是什么?试着从技术的、CRUD的方式(如AddCustomer)中摆脱出来,更多地考虑周围的业务意图。

正如Giacomo Tesio所指出的,DDD在业务逻辑中具有一定复杂性的域中显示其价值。如果您的所有行为都可以映射到CRUD,那么将其保留为CRUD。否则,在你的领域寻找行为,而不是关注数据。你的实体和值对象应该尽可能地公开行为和隐藏状态。请反复阅读参考文章:Vaughn Vernon的有效聚合设计