我是 DDD 初学者,我有一个遗留项目,肯定会从适当的域层中受益。必须修改应用程序以支持多个应用程序和 UI 层。领域逻辑目前是使用事务脚本模式实现的。基本上我继承了一个不允许更改的数据库结构,新应用程序应该是旧应用程序的替代品。
我在该领域的一小部分偶然发现了一个有趣的建模问题,我相信有经验的 DDD 从业者会发现它很有趣。我不能对这个问题太具体,所以我将描述一个与我的问题非常匹配的问题。
问题描述
假设我们应该管理一个产品集合。产品由 id 标识,它们包含一些描述,并且每个产品都有一些与之关联的图像。棘手的部分来了。图像及其内容物理存储在数据库中,因此它们是巨大的数据块。(现在让我们忽略将图像存储在数据库中的好坏,这只是一个示例)。在添加/编辑/删除产品时必须强制执行一些不变量。
添加产品
- 一个产品只有在它有与之关联的图像时才有效,如果不添加图像,则不应允许输入新产品
- 每个产品必须与 5 张图片相关联,不多也不少。
- 必须保持与产品相关的图像顺序
编辑产品
- 现有产品的图片可以更换,但关联图片的数量和顺序要保持
移除产品
- 删除产品时,也应删除与其关联的所有图像
考虑的解决方案
解决方案1:
对这些概念建模的最简单方法如下。产品就是 AR。与产品关联的图像可以通过产品访问和修改,因此产品负责执行 5 个图像规则。这种方法的优点是无法创建或编辑无效产品以使其无效,并且在删除产品时不会留下任何图像。因此,如果在事务边界周围形成聚合。这种方法的问题在于,在绝大多数情况下,UI 只需要显示产品列表,并且可能需要修改它们的描述。UI 很少需要显示或修改与产品关联的图像。所以 95% 的情况下大量不必要的数据会被加载到内存中。
延迟加载?域模型应该用一种没有支持延迟加载的 ORM 工具的语言来实现。实现我自己的延迟加载机制?域对象不应该知道它们被持久化的方式或者它们是否被持久化。相反,Vaughn Vernon 推荐了解决方案 2。
解决方案2:
使用这种方法可以解决查询性能问题,方法是支持小聚合并按照身份规则引用其他聚合。Vaughn Vernon 有很多文章描述了如何实现这一点。
聚合分为两部分 Product 和 ImageSet。它们都将 ProductId 引用为值对象。Product 将负责执行 no product without Images 规则,ImageSet 将执行 no ImageSet without 5 images 规则。查询不再是问题,只有在服务需要时才会检索 ImageSet。然而,这个问题比弗农在他的文章(0...N 关联)中描述的要复杂得多。问题是创建产品会导致修改或创建 2 个聚合,这消除了围绕事务边界对聚合建模的目的。添加新产品的服务将负责事务管理。
解决方案3:
最终的解决方案是使用有界上下文。因此,为简单起见,我们将它们命名为 BC1 和 BC2。在 BC1 中,产品只包含 ProductDetails。有兴趣查询产品的详细信息并可能修改它们的服务将使用 BC1(BC1 中的 ProductRepository 不允许添加或删除产品,只允许查询/修改现有产品)。在 BC2 中,产品将包含 ProductDetails 和与之关联的图像。因此,对添加/删除产品以及修改/检索其图像感兴趣的服务将使用 BC2。共同的价值对象和实体将在这 2 个 BC 之间共享。
该解决方案将解决所有事务一致性和查询性能问题。但是,我不确定是否应该根据他们的定义创建 BC 以应对此类问题。
对于这个冗长的问题,我很抱歉,但我觉得我真的应该指出我已经考虑过哪些类型的解决方案。抱歉链接的图片,我还不允许上传图片。