我是域驱动设计的新手,所以如果这个问题是微不足道的,请原谅。我正在阅读规范模式,我相信理解它的意图。网络上的大多数示例,在两个地方显示了它的用法:
内部存储库方法
内部域服务/应用程序服务。
但这只有在 EF 实体和域对象相同时才有效。而且我猜这不是一个好的做法(使用 EF 实体作为域对象)。现在我的问题是——
我们应该为域对象和 EF 实体编写不同的规范规则,还是有办法为两者重用相同的规则?我想如果我们不使用 c# 表达式并使用反射,我们可以以某种方式实现。
我是域驱动设计的新手,所以如果这个问题是微不足道的,请原谅。我正在阅读规范模式,我相信理解它的意图。网络上的大多数示例,在两个地方显示了它的用法:
内部存储库方法
内部域服务/应用程序服务。
但这只有在 EF 实体和域对象相同时才有效。而且我猜这不是一个好的做法(使用 EF 实体作为域对象)。现在我的问题是——
我们应该为域对象和 EF 实体编写不同的规范规则,还是有办法为两者重用相同的规则?我想如果我们不使用 c# 表达式并使用反射,我们可以以某种方式实现。
这是一个贴近我心的话题。(直接答案如下)规范模式是不可知的。它所做的只是确定您定义(指定)的条件是否得到满足。它在两个地方都很有用。我看到规范的两个主要好处:它命名您的业务规则,它隐藏(封装)您的实现。后续好处是,如果您希望或提供易于理解的查询或规则列表,您可以锁定您的存储库。
应用于域对象的规范与用作查询的规范略有不同,但相当接近。“QuerySpecification”应包含目标存储库实现 (EF) 可以在数据库中获取和执行并返回 0 个或多个匹配项的查询语言。
应用于域对象,它更常用于检查特定对象是否“符合规范”或过滤列表中的适用对象(排除或包含)。用法看起来不同,或者是同一概念的不同方面。
我恭敬地不同意 Zoran 的建议。返回 IQueryable 意味着您正在为应用程序代码(可能是其他人的应用程序)提供直接查询您的数据存储的能力。Lazy vs Eager loading 也变成了一个真正的纠结。我认为你失去了封装和实现隔离。
还要记住,EF 代表 Repository 和 UnitOfWork 的实现。DbContext 是工作单元,而 DbSet 是存储库。您应用自己的 DDD 接口的目标是确保您的域不依赖于 EF 实现,因此您的集成代码应该非常小。
所以,直接回答。
我建议向您的 Repository 接口添加一个接受 IQuerySpecification 并返回 T 的方法。您的实际规范是您的域的一部分。这些是您的组织需要的数据组合的规则。您可以将它们放在“共享内核”(库)中,或者根据您的需要将它们驻留在您的应用程序中。
请注意,理想情况下,通过使用查询规范,您可以消除所有经常弹出的特殊用途存储库功能,因为规范将查询的事物与查询分开。埃里克埃文斯写得更好:
规范的中心思想是将如何匹配候选对象的语句与其匹配的候选对象分开。除了在选择方面的有用性外,它对于验证和按订单构建也很有价值(https://www.martinfowler.com/apsupp/spec.pdf)
另请参阅https://matt.berther.io/2005/03/25/the-specification-pattern-a-primer/
请记住,在使用 EF 存储库时,默认选项已经可用 - 让存储库返回IQueryable<T>
而不是IEnumerable<T>
. 许多程序员对此感到恐惧,但如果这样做,那么 LINQ 就会成为您在基础设施层中的规范。
然后,在域层中,您可以应用规范和规则模式来封装应测试域对象的条件和规则。
还有其他技术,但这种组合是我通常在我的项目中应用的。