DDD、值对象和ORM

17

价值对象没有身份。ORM 需要身份来更新数据库。

如何欺骗 ORM?

(将值对象的 ID 标记为“internal”不起作用,因为 ORM 存在于不同的程序集中,将其移动到相同的程序集中是不可接受的。)

提前感谢。

6个回答

55

当Eric Evans谈到“实体具有身份,值对象没有”,他所说的并不是数据库中的ID列 - 他所说的是身份作为一个概念

值对象没有概念上的身份。这并不意味着它们不应该有持久化身份。不要让持久化实现影响你对实体和值对象的理解。

请参见我在这里的帖子。


但我想展示代码中概念身份的缺乏。顺便说一句,我之前看过那篇文章。 - Arnis Lapsa
1
澄清您的需求:您想隐藏您的VO对象上的ID属性,但是您需要ORM看到ID属性?两个问题: 1)您的ORM可以访问私有/内部字段吗? (像NHibernate一样) 2)通过“隐藏”VO的ID属性,您能获得多少好处? - Vijay Patel
忘了提 - 我正在使用NHibernate...那么,如何使用NHibernate做到这一点? - Arnis Lapsa
作为一个想法,你可以尝试使用显式接口实现(请参见http://blog.briandicroce.com/2007/11/13/explicit-iterface-members-implementation-in-c)。创建一个带有ID属性的接口,然后在你的实体类上显式实现ID属性。我自己没有尝试过 - 如果它有效,请告诉我! - Vijay Patel

5
据我对DDD的理解,值对象只是将实体进行分区的一种方式。如果一个值对象应该在数据库中使用ID存储,那么它就不是一个值对象。
例如:
领域模型如下(C#):
public class Customer : Entity
{
    public Guid CustomerID { get; }

    public string LastName { get; set; }

    public Address HomeAddress { get; set; }
}

public class Address : ValueObject
{
    public string Street { get; set; }

    public string City { get; set; }

    public string ZipCode { get; set; }
}

对应的数据库表看起来应该是这样的(伪SQL):
CREATE TABLE Customers
(
    CustomerID,

    LastName,

    HomeAddress_Street,

    HomeAddress_City,

    HomeAddress_ZipCode,
)

将地址存储在单独的表格中,您需要将其作为一个实体,并赋予其一个ID。

5
领域模型与数据库是1:1的,但地址仍可以作为值对象存在,并且仍可拥有单独的表格。 - TWith2Sugars
1
不,它不是数据库的1:1映射。 你有一个客户类和一个地址类(这是值对象)。在NHibernate中,值对象被映射为组件。 一旦你有一个需要ID的实体,它就不再是值对象了。 - Frederik Gheysels
8
但如果您需要在表格中保存地址,那么数据库将需要一个ID。仅因为数据库需要一个ID并不意味着对象立即成为实体。 - TWith2Sugars
1
希望这个问题能提供更多信息:https://dev59.com/pHRB5IYBdhLWcg3wQFPu - TWith2Sugars
在这种情况下,如何“隐藏”VO的ID。我不是要完全摆脱它,因为那样我就无法将VO数据存储在另一个表中。 - Arnis Lapsa
显示剩余2条评论

5

个人而言,我将Id字段放在值对象中 - 我将其视为值对象的另一个属性(例如名称,位置等)。

这可能不是真正的DDD,但对我来说有效。


3
我认为这完全没有问题。Eric Evans让大家晕头转向,因为他很容易谈论概念而不给出例子。 - Pepito Fernandez
值对象中的id字段根据DDD并不能唯一标识您的值对象,它只是像其他属性值一样的另一个属性值。 - acearch

0

VO属于实体(Entity)。 我们将使用实体的ID(业务 ID)来跟踪VO。

VO还可以包含其他实体/VO,它代表OO(面向对象编程)的封装。 以E-R,1:N为例,我们可以使用联合表来持久化它。

专注于业务,而不是那些概念。


0
在之前的答案中提到的所有持久化值对象的选项 - 例如将值对象属性作为它们所属的实体表的列进行平铺,或者通过包括数据模型的唯一ID将它们持久化在单独的表中 - 都是有效的并且解释得很清楚。当然,这些选项通常适用于特定的底层数据库技术,这是一个很大的优点。
但我认为至少值得提到一些其他选项,这些选项在许多情况下可能足够并且易于实现:
将值对象存储为JSON表示形式
当然,这取决于您的技术限制,但现在许多数据库以及ORM解决方案甚至提供内置支持JSON表示形式。有些甚至包括搜索选项。如果您不期望有大量的项目,您甚至可以使用此方法来处理实体内部值对象列表,通过将该列表作为对象的JSON集合直接持久化在实体表中。
除了JSON之外,当然还支持其他格式(例如纯文本或XML),但从我的经验来看,我发现JSON最舒适。
使用基于文档的存储解决方案
值得一提的是,选择基于文档的数据库技术(例如MongoDB)也为持久化领域模型实体提供了新的选项,因为它允许将聚合作为整个文档进行持久化,包括其所有子实体和/或值对象。

0

你有两个选项:

  • 值对象保存在同一个聚合根表中
  • 使用聚合根作为ID,分离表格

例如,对于你的示例:

public class Customer : Entity
{
    public Guid CustomerID { get; }
    public string LastName { get; set; }
    public Address HomeAddress { get; set; }
}

public class Address : ValueObject
{
    public string Street { get; set; }
    public string City { get; set; }
    public string ZipCode { get; set; }
}

选项1(伪SQL):

CREATE​ ​TABLE​ Customer (
      // aggregate root
​     customerId ​int​ ​NOT​ ​NULL​,
      lastName VARCHAR(30),

      // value object
      street VARCHAR(100),
      city VARCHAR(50),
      zip VARCHAR(10)
​     ​CONSTRAINT​ PK_Customer ​PRIMARY​ ​KEY​ (customerId)
​   )

选项2(伪SQL):

// aggregate root
CREATE​ ​TABLE​ Customer (
​   customerId ​int​ ​NOT​ ​NULL​,
    lastName VARCHAR(30)
    CONSTRAINT​ PK_Customer ​PRIMARY​ ​KEY​ (customerId)
    )

// value object
CREATE​ ​TABLE​ Address (     
​     customerId ​int​ ​NOT​ ​NULL​, // same ID from Customer

​     street VARCHAR(100),
      city VARCHAR(50),
      zip VARCHAR(10)
      ​CONSTRAINT​ PK_Address ​PRIMARY​ ​KEY​ (customerId)
    )
  • 然后您可以创建一个toDomain(sqlResult)函数,将查询结果转换为您的域对象
  • 默认情况下尝试使用单表方法

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接