DDD:主键(Ids)和ORM(例如,NHibernate)

时间:2009-05-13 20:19:12

标签: nhibernate architecture domain-driven-design

为什么在域实体中有一个Id字段被认为是可以的? 我已经看到了几个提供基类的解决方案,其中包含基于Id和Id的GetHashCode / Equals。

我对域模型的理解是它应该只包含与域相关的东西。虽然在极少数情况下(可跟踪订单)ID很有意义,但大多数时候除了在DB / UI上引用对象的简单方法外,它们不提供任何其他内容。

我也没有看到Equals / GetHashCode的好处,因为Identity Map实现应该保证引用相等 仍然是id等。

奇怪的是,我不能轻易找到其他人对这个主题的看法,所以我在这里问。在域实体中使用非域相关ID的一般意见是什么?如果我不向我的域实体添加ID,那么NHibernate有任何问题吗?

更新

感谢您的回答。

其中一些建议使用Id是ORM进行数据库更新的唯一方法。我不认为是这种情况。 ORM已经跟踪从DB加载的所有实体,因此它应该能够在需要时很容易地在内部获取Id。

更新2:

回答正义和类似的观点: 如果我们有一个Web应用程序并且需要在会话之间引用实体的方法怎么办?喜欢edit / resource / id?

好吧,我认为这是受限制的UI /环境的特定需求,而不是域模型的需要。使用GetIdentitity方法(与Load(identity)方法一致)的应用程序服务或存储库似乎足以满足此方案。

8 个答案:

答案 0 :(得分:4)

我只能谈谈NHibernate。在那里,您需要一个主键字段,如果您获取业务数据(不推荐)或没有商业含义的代理键,则由您决定。

典型情况是:

  • 数据库生成的自动递增值
  • 由NHibernate生成的guid
  • 由业务逻辑生成的guid

拥有唯一ID是一个很大的优势。当您将对象传递给客户端并返回服务器时,您不能依赖于内存标识。您甚至可以在同一业务实例的内存中有多个实例。业务实例由id标识。

基于身份的比较是一个品味问题。我个人喜欢简单可靠的代码,基于身份的比较 简单可靠,而深度比较总是有点风险,容易出错,无法维护。所以你最终得到operator ==比较内存标识和Equals比较业务(和数据库)身份。

NHibernate是一种将类模型映射到我所知道的关系数据库的不那么具有侵入性的方法。在我们的大型项目中,我们有主键和版本属性(它们用于乐观锁定)。您可能会认为这是侵入性的,因为它不用于业务逻辑。

NH不需要具有这些属性。(但它需要其中一个属性作为主键。)但请考虑:

  • 它的效果更好,例如。只有适当的属性才能实现悲观锁定,
  • 更快:int id在许多数据库上表现更好,然后是其他数据类型
  • 更容易:出于几个原因,不鼓励对企业有意义的财产。

那么为什么要让你的生活比必要的更难?


编辑:阅读the documentation,发现NHibernate 需要持久类中的id属性!如果您没有标识符you can't use some features。建议拥有它,它只会让你的生活更轻松。

答案 1 :(得分:3)

通常,您不会拥有一个真正的域模型。您将拥有无数的并发和顺序会话,这些会话必须协调,并且每个会话都包含其自己的微型,隔离的事务域模型。在两个并发会话之间或两个顺序会话之间不会有相同的对象实例。但是您需要一些方法来确保您可以将信息从一个会话移动到另一个会话。

当然,一个例子是/ resource / list和/ resource / show / id对的URL(对应于asp.net mvc中的一对适当的控制器动作)。从一个移动到另一个,有两个完全独立的会话,但它们需要相互通信(通过HTTP和浏览器)。

即使不使用数据库,您的域模型对象仍然需要某种形式的身份,因为您仍然会拥有众多微型孤立域模型,而不是真正的域模型;并且您将在同一时间和一个接一个的独立会话中拥有所有这些域模型。

对象相同性是不够的,因为会话之间的对象不一样,即使它们所代表的基础概念实体是同一个实体。

答案 2 :(得分:1)

在Nh中,您不能对分离的实例使用引用相等性。这会让你在想要使用这种行为的客户中使用NH(在我看来这是正确的行为,NH并不依赖于魔法!)。

在Web应用程序或基于会话的用法中,您可以在一个ISession的范围内转到数据库,我认为我说你可以依赖引用相等是正确的。但是在智能客户端场景中,或者更具体地说,在两个ISession共享实例的任何场景中(一个从db加载或插入到db中,然后传递给第二个),您需要同时存储db id字段实例。

下面的第二个会话不知道它正在传递的实例已经插入并需要更新,即使你把它更改为更新,我也认为我认为它不会知道班级的DB ID。

class SomeEntityWithNoId
{
public  blah etc {}
}

class dao
{

void DateMethod()
{
var s1 = sessionFactory.OpenSession();
var instance = new SomeEntityWithNoId();
instance.Blah = "First time, you need to insert";
s1.Save(s1); //now instance is persisted, has a DB ID such as identity or Hilo or whatever
s1.close();
var s2 = sessionFactory.OpenSession();
s2.Blah = "Now update, going to find that pretty hard as the 2nd session won't know what ID to use for the UPDATE statement";

s2.Update(instance); //results in boom! exception, the second session has no idea *how* to update this instance.




}
}

如果您担心ID字段,除了持久性问题之外,对于除了持久性问题之外的其他任何事情,你认为它是无用的,你可以将它们设为私有,所以至少这种行为和语义是封装的。您可能会发现这种方法会给TDD带来一些摩擦。

我认为至少对于有关NH的ID地图是正确的。如果实例在首次连接到会话时是暂时的,则您的类型不需要将其ID ID存储在字段中。我认为会话将​​知道如何管理它与数据库的关系。我相信你可以用一个测试来证明这种行为; p

答案 3 :(得分:0)

您必须以某种方式拥有它,以便ORM可以返回数据库来执行更新;我想它可能是私有的并隐藏在基类中,但这有点违反了POCO的概念。

答案 4 :(得分:0)

假设您的ID是您的主键,如果没有它,您将无法将任何数据保存回数据库 就equals和GetHashCode而言,这些是用于创建自定义排序的列表中的排序。

  

感谢您的回答。

     

其中一些建议使用Id是ORM进行数据库更新的唯一方法。我不认为是这种情况。 ORM已经跟踪从DB加载的所有实体,因此它应该能够在需要时很容易地在内部获取Id。

如果是这样的话,我会非常惊讶。通常它只从您定义的属性创建一个insert语句。

答案 5 :(得分:0)

没有其他选项可以在没有标识的情况下获取相同的对象。例如,人们有社会安全号码或个人代码或某些独特的东西,我们可以通过它来识别它们。

您可能会使用自然身份,但这很容易出错。

只需将Id重命名为Identity即可。它在域模型中看起来更好。

实体被称为实体,因为它们具有身份。

答案 6 :(得分:0)

我通常在域中包含Id作为基本实体类。我不知道使用ORM的任何其他方式。

域中的对象通过引用已经是唯一的,使用id来确定唯一性/相等性在实践中并没有那么不同。到目前为止,我还没有遇到过这种方法的严重副作用。

答案 7 :(得分:-1)

您还可以使用多个字段来建立标识,称为复合键。但是,这需要更多的工作。

了解他们here