【问题标题】:Mapping between the persistence model and the domain model持久化模型和领域模型之间的映射
【发布时间】:2014-08-23 12:47:47
【问题描述】:

我已经阅读了大量有关领域驱动设计的文章,并且使用该设计完成了一些相当复杂的项目。他们都有自己的缺陷和反模式,这些缺陷和反模式在此过程中得以实现。可以理解,因为这是一个学习过程。然而,我被困在一个似乎无法通过谷歌解决的主要概念(也许我只是没有想出正确的搜索词)或我自己的反复试验。

我已经阅读了几篇文章,它们强调将您的领域模型和持久性模型分开。不要让诸如 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#,因为我确信大多数示例将使用这两种语言中的一种。

【问题讨论】:

  • 人们通常使用单独的“持久 ID”并将其用于数据库中的外键。
  • 但这不是违反“在您的域模型中没有仅数据库规则”吗?既然除了坚持之外没有任何理由存在,它不应该在那里,对吧?
  • 是的,有点,但您不应该对此过于严格,因为这是您无法规避的技术要求。对于既不公开公开也不在内部用于状态保持的数据库 ID 使用私有字段很容易。例如,它可以添加到公共层超类中,因此隐藏到任何“真实”域对象中。

标签: domain-driven-design


【解决方案1】:

根据领域驱动设计:

关于实体和价值观的一些话

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

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

例如:

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

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

ProductAmount 是一个值,Order 是一个实体(具体看具体情况)。

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

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

数据库

好的,那么数据库及其作用呢。

今天的数据库不仅是数据存储,而且是确保数据完整性、事务处理等的复杂机制。很多时候,应用程序只需将并发访问等复杂任务“广播”到数据库即可解决。

由于数据库经常用于生成键值的相同原因,它们提出了良好的可靠机制来获取新的唯一值。 所以,keys是由数据库生成的,因为它很容易实现,keys被用来将领域模型实体映射到持久化模型,因为它是自然的。

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

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

持久性和域

在谈论域时,我们为什么要考虑数据库(甚至是关系数据库!)? Eric Evans 在他的 DDD 书中说“不要面对技术和范式”。

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

我们只需要处理它。

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

【讨论】:

    【解决方案2】:

    我认为答案相对简单。假设你没有持久化模型,那么如何识别你的领域模型呢?

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

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

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

    【讨论】:

      【解决方案3】:

      您不需要在 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,也许您可​​以自动将此代理主键添加到所有实体。代理主键应自动生成(例如,自动递增)并用于表中的关系。

      【讨论】:

        猜你喜欢
        • 2012-12-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-07
        • 2015-11-12
        • 2014-01-23
        • 1970-01-01
        相关资源
        最近更新 更多