Domain driven design 持久性模型和域模型之间的映射

Domain driven design 持久性模型和域模型之间的映射,domain-driven-design,Domain Driven Design,我已经阅读了大量关于领域驱动设计的书籍,并用该设计完成了一些相当复杂的项目。所有这些都有自己的缺陷和反模式,这些缺陷和反模式都是在这一过程中认识到的。可以理解,因为这是一个学习过程。一、 然而,我被困在一个主要的概念上,我似乎无法通过谷歌(也许我只是没有想出正确的搜索词)或我自己的尝试和错误来解决 我读过几篇文章,它们强调要将域模型和持久性模型分开。不要让ID之类的东西泄漏到域模型中,除非该ID有域用途。有了该策略,如何在实践中持久化域模型?我读过的所有文章都是抽象地谈论这一点,但我找不到一个不

我已经阅读了大量关于领域驱动设计的书籍,并用该设计完成了一些相当复杂的项目。所有这些都有自己的缺陷和反模式,这些缺陷和反模式都是在这一过程中认识到的。可以理解,因为这是一个学习过程。一、 然而,我被困在一个主要的概念上,我似乎无法通过谷歌(也许我只是没有想出正确的搜索词)或我自己的尝试和错误来解决

我读过几篇文章,它们强调要将域模型和持久性模型分开。不要让ID之类的东西泄漏到域模型中,除非该ID有域用途。有了该策略,如何在实践中持久化域模型?我读过的所有文章都是抽象地谈论这一点,但我找不到一个不违反这一点的具体例子

我正在构建一个相对较大且复杂的web应用程序,希望实现我所能实现的“最佳”域和持久性分离。我使用的是一个手动的ORM(是的,是的,我知道-我不应该-胡说八道-但是,底层的表和查询太复杂了,无法很好地使用EF或NHibernate之类的东西)。在一所大型大学的会计包中,我有以下结构的总账日记账分录:

 Public Class Journal

    Public Property AccountCode As SFSAccountCode = Nothing

    Public Property Amount As Decimal = 0

    Public Property BudgetCategory As BudgetCategory = Nothing

    Public Property [Date] As DateTime = Nothing

    Public Property ChildAccount As ChildAccount = Nothing

    Public Property Description As String = ""

    Public Property FiscalYear As SFSFiscalYear = Nothing

    Public Property Fund As Fund = Nothing

    Public Property JournalID As Int32 = -1

    Public Property Notes As String = ""

    Public Property Program As String = ""

    Public Property Source As JournalSource = Nothing

    Public Property Status As JournalEntryStatus = JournalEntryStatus.Open

    Public Property TransactionType As TransactionType = Nothing
End Class
如果不包括uniqueID(JournalID),如何将域模型中的实例映射到持久性模型中的实例?根据我的理解,你要考虑一个物体的不变量。简单,如果对象只有一个或两个属性,如字符串或整数。很明显,我有很多属性,其中几个属性本身就是域模型

我肯定有一些我刚刚错过的关键概念——有人能给我指一个资源(附带具体代码!)来帮助解释如何在一个有数据库ID的持久模型和一个没有数据库ID的域模型之间进行映射吗


顺便说一句,是的,我知道我的属性应该是好的域设计的私有集。一旦我能够更好地找出持久性和域之间的映射,它们就会出现。我碰巧用VB.NET编写代码,但可以很好地阅读Java或C,因为我确信大多数示例将使用这两种语言之一。

根据域驱动设计:

关于实体和值的一些词 有实体对象(实体)和值对象(值)。值由一组值(或字段)标识。因此,如果我们有两个具有相同字段的值对象,它们对我们来说并不是不可伪装的(就像现实生活中的模型一样)。值对象通常是不可变的

实体对象不由包含的值标识。实体对象由其自身的唯一存在标识。我们不能说两个同名的人代表一个真正的男人

例如:

package se.citerus.dddsample.domain.model.cargo;
...
public class Cargo implements Entity<Cargo> {

  private TrackingId trackingId;

  ...

  /**
   * The tracking id is the identity of this entity, and is unique.
   * 
   * @return Tracking id.
   */
  public TrackingId trackingId() {
    return trackingId;
  }

  ...

  Cargo() {
    // Needed by Hibernate
  }

  // Auto-generated surrogate key
  private Long id;

}
类ProductAmountPair{
公共产品产品{get;set;}
公共整数金额{get;set;}
}
阶级秩序{
公共int Id;
公共IList{get;set;}
}
ProductAmount是一个值,Order是一个实体(实际上它取决于具体情况)

要识别实体对象,必须确定一些唯一的值(键)。在某些域中,密钥是从外部(从现实生活中)给定的,但另一个域需要生成密钥,因此应用程序本身定义了一种方法来实现这一点

所以,结论是:对于实体,我们必须引入关键字段来识别它们。这是必要的,因为实体无法通过其包含的一组值来识别。

数据库 好的,那个么数据库及其角色呢

如今,数据库不仅是数据存储,而且是确保数据完整性、事务处理等的复杂机制。通常,并发访问等复杂任务都是由应用程序通过将它们“广播”到数据库来解决的

出于同样的原因,数据库通常用于生成键值,它们提出了获得新的唯一值的可靠机制。 所以,键是由数据库生成的,因为它易于实现,键是用来将域模型实体映射到持久性模型的,因为它是自然的。

可以说,DB引擎需要为每个表创建主键,所以域中的每个类都应该有键字段,所以它是实体。但是:

  • 关系数据库引擎使用主键进行引用,它不应该以任何方式影响域模型
  • 对于实值类型(如描述的ProductAmount类),不应使用存储在DB键中的数据从数据库中获取相应的对象。它没有意义,因为值对象不能通过某个键(通过值类型的定义)来识别。但数据库中存储的密钥用于加载对象关联。所以这个操作必须被ORM封装。此外,从数据库中获取值对象的唯一方法是从某个实体的字段中获取它
持久性与域 为什么我们在讨论域时应该考虑数据库(甚至关系数据库?)。Eric Evans在他的DDD书中说“不要面对技术和范例”

有时,由于强大的性能或可实现性需求,我们不得不放弃一些DDD纯度和清晰性,使域模型更“相关”(我主要是指类表映射)

我们只需要处理它


出于同样的原因,我们可以承认实体关键领域的部分“技术”角色。但我们也承认它作为域标识属性的主要作用。

您不需要在DDD中的域类之外创建另一个持久性类。如果您在中阅读Eric Evan的书中的DDD示例,您可以看到实体除了自然id之外还有代理id。例如:

package se.citerus.dddsample.domain.model.cargo;
...
public class Cargo implements Entity<Cargo> {

  private TrackingId trackingId;

  ...

  /**
   * The tracking id is the identity of this entity, and is unique.
   * 
   * @return Tracking id.
   */
  public TrackingId trackingId() {
    return trackingId;
  }

  ...

  Cargo() {
    // Needed by Hibernate
  }

  // Auto-generated surrogate key
  private Long id;

}
如果您创建自己的ORM,也许可以自动将此代理主键添加到所有实体。代理主键应自动生成(对于