2

我是 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 以应对此类问题。

对于这个冗长的问题,我很抱歉,但我觉得我真的应该指出我已经考虑过哪些类型的解决方案。抱歉链接的图片,我还不允许上传图片。

4

1 回答 1

0

您的用例中的一个重要观察结果是第一个解决方案的问题与应用程序的查询端隔离。没有理由使用与用于查询的模型相同的模型来处理命令和强制执行约束。读取模型模式可用于将读取与写入分开,这将允许您为特定的 UI 要求创建特定的读取模型,并且读取模型不会影响您的域模型。虽然使用与写入相同的模型来读取是很诱人的,尤其是考虑到大多数 ORM 支持复杂的查询并给出DRY 原则,但实际上将读取模型与可执行域模型分开要容易得多。

此外,Vaughn Vernon 的系列文章是了解聚合设计复杂性的重要资源,但文章的中心重点是如何根据行为需求而不是查询需求对聚合进行分区。

于 2012-07-20T19:11:39.867 回答