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

时间:2014-08-23 12:47:47

标签: domain-driven-design

我已经完成了关于域驱动设计的IMMENSE阅读量,并且使用该设计完成了一些相当复杂的项目。所有这些都有他们的缺陷和反模式,沿途实现。可以理解,因为这是一个学习过程。但是,我仍然坚持一个我似乎无法通过谷歌解决的主要概念(也许我只是没有提出正确的搜索条件)或者我自己的反复试验。

我已经阅读了几篇强调声称将您的域模型和持久性模型分开的文章。除非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的持久模型与域之间进行映射模特没有?

顺便说一下,是的,我知道我的属性应该是私有的好域设计。他们将有一次我可以更好地找出持久性和域之间的映射。我碰巧在VB.NET中编写代码,但可以读取Java或C#,因为我确信大多数示例都使用这两种语言中的一种。

3 个答案:

答案 0 :(得分:5)

根据领域驱动设计:

关于实体和值的一些话

有权利对象(实体)和价值对象(价值)。值由一组值(或字段)标识。因此,如果我们有两个具有相同字段的值对象,它们对我们来说并不是不可分割的(作为现实生活中的模型)。值对象通常是不可变的。

实体对象不是由包含的值标识的。实体对象由其自身唯一的存在来标识。我们不能说两个同名的人提出单身真人。

例如:

class ProductAmountPair {
    public Product Product { get; set; }
    public int Amount { get; set; }
}

class Order {
    public int Id;
    public IList<ProductAmount> { get; set; }
}

ProductAmount是一个值,Order是一个实体(实际上它取决于具体情况)。

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

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

数据库

好的,数据库及其角色怎么样。

今天,数据库不仅仅是数据存储,而且是确保数据完整性,事务处理等的复杂机制。通常,复杂的任务(如并发访问)仅通过&#34;广播&#34;他们到数据库。

出于同样的原因,数据库经常被用来生成关键值,他们提出了获得新的唯一值的良好可靠机制。 因此,密钥是由数据库生成的,因为它易于实现,并且密钥用于将域模型实体映射到持久性模型,因为它很自然。

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

  • 关系数据库引擎使用主键进行引用,不应以任何方式影响域模型
  • 对于实值类型(如描述的ProductAmount类),不应使用存储在DB键中从数据库中获取相应的对象。它没有任何意义,因为某些键(通过值类型的定义)无法识别值对象。但是存储在数据库中的密钥用于加载对象关联。因此,此操作必须由ORM封装。此外,从数据库获取值对象的唯一方法是从某个实体的字段中获取它。

持久性和域

为什么在谈论域​​时我们应该考虑数据库(甚至是关系数据库!)?在他的DDD书中,埃里克埃文斯说,不要面对技术和范例&#34;。

有时,由于强大的性能或可靠性要求,我们必须放弃一些DDD纯度和清晰度,并使域模型更多(例如)&#34;关系&#34; (我的意思是大多数类表映射)。

我们必须处理它。

出于同样的原因,我们可以承认部分&#34; techincal&#34;实体关键领域的作用。但我们也承认其主要作用是域名识别属性。

答案 1 :(得分:2)

我认为答案在我看来相对简单。假设您没有持久性模型,那么如何识别域模型呢?

使用您自己的示例,您如何识别域中的日记对象?也许,JournalID是唯一的选择,然后您的域名需要它,您应该非常乐意添加到域模型中。

在其他情况下,例如,您有一个Order域模型,您的域使用OrderReferenceNumber(字符串值)来标识它。例如,您在向客户发送确认电子邮件时将此号码作为订单的标识符。另一方面,在持久性模型上,您有一个OrderID(长值)作为主键。在这种情况下,您的域模型不需要了解它,也不应该泄漏到您的域模型中。

希望这会有所帮助,如果您有复杂的域模型而不考虑EF或NHibernate,可以试试这个,FluentMap

答案 2 :(得分:1)

您无需在DDD中使用域类创建另一个持久性类。如果您在http://dddsample.sourceforge.net/中阅读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;

}

代理ID由Hibernate自动生成。这可以在Cargo.hbm.xml

中看到
<hibernate-mapping default-access="field">
    <class name="se.citerus.dddsample.domain.model.cargo.Cargo" table="Cargo">
       <id name="id" column="id">
          <generator class="org.hibernate.id.IdentityGenerator"/>
       </id>
       ...
    </class>
</hibernate>

如果您创建自己的ORM,也许您可​​以自动将此代理主键添加到所有实体。代理主键应自动生成(例如,自动递增)并用于表中的关系。