在DDD(领域驱动设计)中,如果值对象位于具有代理主键的单独表中,如何持久化值对象

问题描述 投票:0回答:1

根据 DDD 定义,值对象由其属性值决定,而不是由 ID 决定。在我的应用程序中,最常见的是某种下拉菜单,其中填充了值对象并由用户选择。这些下拉菜单通常使用代理键进行连接,代理键由查询端的数据库引擎自动生成(或在涉及 Web 请求时为 GET)。另一方面,当用户想要保留一些数据(在 POST 期间)时,用户只给出自动生成的 Id。现在,由于属性的值丢失,并且我们只有 Id,因此如果不先从数据库中检索其值,然后使用聚合根存储库将其持久化,就很难初始化值对象。在这种情况下,Id 更像是元数据,而不是唯一标识符,类似于 CreatedDatetime 或 CreatedByUser 字段,并且,对我个人而言,此类字段位于域中是没有意义的,因为它们更像是技术问题。这是处理这类事情的普遍接受的方式吗?您如何实现值对象的存储库,甚至更大的问题是如何将其与数据库生成的 ID 等技术问题分离?

domain-driven-design ddd-repositories
1个回答
0
投票

免责声明:我认为关于 DDD 的问题很少有答案,而是对讨论的鼓励。所以这是我的观点,可能存在缺陷和偏见。这不是一个答案。但如果你真的喜欢我的意见,你可以选择它作为接受的答案......这将是你解决问题所需的实用主义:-)

我给你举一个我的项目的例子:体育比赛管理。有一个年龄组的概念,例如“18岁以下女性”。您可以说这是一个值对象,因为它是由其值(性别、最大年龄、最小年龄)定义的,并且没有生命周期。但我引入了一个 Id,因为我需要在外键中引用它,并且我不想在数据库中有 2 列(性别和 maxAge)来引用它。该项目进行几年后,决定更改最低年龄。在此之前,您必须年满 16 岁才能参与此年龄组,但他们将其降低到 15 岁。因此,我们在age_group 表/AgeGroup ValueObject 中更改了这一点。

...所以这是第二个指标(在拥有 Id 之后),表明它可能不是值对象。事实证明还有第三个:多个实体引用同一实例。当我们更改最低年龄时,这意味着我们也更改了前几个赛季某些比赛的最低年龄......不好!

邮政地址是值对象还是实体有很多讨论。这两种观点都有很好的论据。但如果 2 个人住在同一所房子里,并且 person.address 指向两个人的同一个 Address 实例,那么它肯定是一个实体。

带有 Id 的 ValueObject 的示例: 一个人可以有不止一种联系方式。所以有 person.contactInfos 一个 ContactInfo 数组,它有一个类型(“电子邮件”、“电话”等)和 URL。为了简单起见,我给了他们一个 Id 只是为了让 ORM 更容易。但是,当我更新某人的联系信息或更改顺序时,我只需删除旧的并创建新的。因为这就是值对象:一次性且可交换的。对于应用程序中的每个值对象实例。您应该能够删除它,然后创建一个具有相同值的实体,将其附加到一个实体,事情就和以前一样了。

总结一下:

  • 出于技术原因,我对具有 Id 的值对象没有任何问题,但绝不能使用该 Id。如果您通过 HTTP 端点寻址某人的地址,它应该类似于 /person/{personId}/address 而不是 /address/{addressId}。
  • 如果多个实体引用同一个值对象,那么它就不是值对象。考虑货币金额:货币和金额的组合。如果您有一些汇款,那么您的一侧帐户将有货币借记,另一帐户将有货币贷记。这两个实体都有一个代表货币和金额的 MonetaryValue。但它是不同的实例......只是具有相同的值。

对于您的问题,我建议遵循以下决策路径:

  • 如果模型对象实际上只是一个字符串,那么它就是一个值对象。也许只是一个枚举?

  • 如果同一个模型对象被多个实体引用,那么它就是一个实体。您可能想引入“具有唯一性约束的不可变实体”的概念,就像我的 AgeGroup 示例一样,它对性别 + maxAge 具有唯一性约束

  • 如果您的模型对象有一个 Id 只是为了简化 ORM,但仅附加到一个实体,并且您可以删除该实例并重新创建它而不会产生任何副作用,那么它就是一个具有 Id 的值对象。

© www.soinside.com 2019 - 2024. All rights reserved.