以下代码是不是违反持久性无知规则

Posted

技术标签:

【中文标题】以下代码是不是违反持久性无知规则【英文标题】:Does the following code violate Persistence Ignorance rule以下代码是否违反持久性无知规则 【发布时间】:2012-10-03 19:06:53 【问题描述】:

a) 域实体不应该包含与持久性相关的代码,因此它们应该是Persistence Ignorant PI。但假设 Domain Model DM 是使用 Entity Framework 设计的,并假设 service layer 执行 CRUD 操作POCO 域实体 通过 Linq-to-Entities,我们认为 服务层 是直接访问 DAL 还是通过 Domain型号

class CustomerService

       public string doSomething( ... )
       
                ...
                var customer = context.Customers.Where( ... );
                ...
        
       ...

b) 在 DM 中使用 Linq-to-Entities 是否违反 PI 规则?例如,以下Customer 实体是否违反 PI:

class Customer

       public string InterestedWhatOtherCustomerOrdered( ... )
       
                ...
                var orders = context.Orders.Where( ... ); // does this violate PI rule?
                ...
        
       ...

回复卢克·麦格雷戈:

一)

是的,因为它直接引用上下文。更好的方法是 是使用 Customer 上的内部导航属性来执行 同样的动作,

所以导航属性应该联系上下文?!但是由于 导航属性 也存在于域模型中,那么我们难道不能说通过直接联系 上下文,它们也会违反 PI 吗?

b) 根据Fowler 的关于Data Mapper 的PEAA 章节,可以从Data Mapper 中提取领域代码所需的任何方法到接口类中,哪个领域代码然后可以使用。当使用 EF 而不是手写的Data Mapper 时,究竟如何做到这一点,这样我们就不会违反 PI

谢谢

【问题讨论】:

【参考方案1】:

是的,它直接引用上下文。更好的方法是使用 Customer 的内部导航属性来执行相同的操作,

class Customer

       public string InterestedWhatOtherCustomerOrdered( ... )
       
                ...
                var orders = this.Orders;
                ...
        
       ...

或者为这个函数创建一个单独的查询类。

【讨论】:

@user702769 导航属性通常被认为是模型的一部分,而不是持久层的一部分。这是因为您可以拥有具有导航属性的类似对象结构并使用完全不同的持久层。导航属性根本没有真正联系上下文(除了一些代理实现),它们描述了我们域中两个实体之间的关系。 EF(和其他形式)对这种关系进行交互,并将其转化为可以持久保存到数据库的东西 @user702769 就数据映射器而言,我不太确定,因为我面前没有这本书,但我想这是指允许我们的域的查询/dao 类型类对持久层执行查询。通过将其转换为接口,我们可以使用查询类,而无需直接引用引用持久层特定实现的类。这是我们可以将实现与使用分离的一种方式。 @user702769 事实上,DDD 主要是关于将组件与域外的解耦,以便我们可以安全地在域内实现更高的内聚 @user702769 IE 允许在可能的情况下删除对域外部问题(例如持久性)的引用,以便我们可以专注于对我们重要的数据、数据以及它与域内其他实体的关系 @user702769 如果上下文完全从持久层的实现中抽象出来,我不认为这会直接违反 PI。但是我仍然认为这是不好的做法,因为这意味着您的模型必须了解上下文。理想情况下,模型应该只知道它描述的数据。【参考方案2】:

a) DDD 中有不同风格的服务。域层服务不应引用实体框架上下文,因为它会将域与特定的持久性方式紧密耦合 - 与域实体相同,见下文。相比之下,应用层服务可以使用上下文(例如调用SaveChanges()),因为它知道当前的工作单元以及何时应该持久化事物。

b) 正如 Luke 所说,是的,它确实违反了 PI,因为实体框架上下文是特定于持久性的。在您的实体中,您应该使用与持久性无关的方式来获取所需的订单。

我不太明白您的 InterestedWhatOtherCustomerOrdered() 方法的作用(为什么要返回一个字符串?...),但您可以:

class Customer

       public string InterestedWhatOtherCustomerOrdered( ... )
       
                ...
                var orders = OrderRepository.GetOtherCustomerOrdersByInterest(...);
                ...
        
       ...

前提是Orders 是一个聚合根。如果不是,您可以使用域层服务,该服务转向基础设施层服务进行查询,或者直接从它们自己的聚合根中询问订单。

【讨论】:

应用层服务不应该也遵守 PI 规则吗?如果是,那么使用 EF 上下文的应用层服务会不会违反 PI 规则(因为 EF 是特定于持久性的)? 当我们谈论 PI 时,我们通常指的是领域层对象的 PI,而不是应用层对象。引用持久层的应用程序层并不少见。但是您是对的,理想情况下,应用程序层应该保护自己免受一个或另一个 ORM 框架的细节影响。定义自己的 IUnitOfWork 可以帮助您做到这一点。 知道为什么(与域层对象相比)应用层服务遵守 PI 的重要性不那么重要吗? 我不知道,也许是因为域被视为“更纯粹”的实体,更重要的是要清除不需要的持久性细节。应用层是比领域更低的层,它也不太可能在另一个架构中重用,因此在大多数情况下,您可以负担得起将应用服务与其他层(当然 UI 除外)紧密耦合。 取决于您在顶部和底部放置的内容;)如果依赖关系向上,则域是最顶层。从洋葱架构的角度来看,依赖关系从外到内,它是最中心的层。

以上是关于以下代码是不是违反持久性无知规则的主要内容,如果未能解决你的问题,请参考以下文章

持久性无知和 DDD 现实

DDD 域实体与持久性实体

具有实体框架和空间数据的持久无知域

究竟啥是“执着无知”?

当使用违反唯一约束的实体调用 persist() 时没有持久性异常

如何使我的代码诊断语法节点操作对关闭的文件起作用?