实体框架的域驱动设计的陷阱


12

我研究的许多DDD教程都涵盖了理论。它们都有基本的代码示例(Pluralsight和类似代码)。

在网络上,也有人尝试创建涵盖EF的DDD的教程。如果您只是简短地学习它们-您很快就会发现它们彼此之间有很大的不同。有些人建议保持应用程序最小化,并避免在EF之上引入其他层(例如,存储库),其他人则决定生成额外的层,甚至通过注入DbContext聚合根甚至违反SRP 。

如果要提出基于意见的问题,我深表歉意,但是...

在实践中,实体框架是功能最强大且使用最广泛的ORM之一。不幸的是,您不会找到涵盖DDD的综合课程。


重要方面:

  • 实体框架可立即使用UoW和存储库(DbSet

  • 使用EF,您的模型具有导航属性

  • 与EF所有的车型都始终可用的关闭DbContext(它们被表示为DbSet

陷阱:

  • 不能保证子模型仅受“聚合根”影响-您的模型具有导航属性,可以修改它们并调用dbContext.SaveChanges()

  • DbContext您可以访问每一个模型,从而规避聚合根

  • 您可以通过将ModelBuilderin 标记为字段来通过in OnModelCreating方法来限制对根对象的子对象的访问-我仍然不认为这是进行DDD的正确方法,而且很难评估这种情况将来可能导致什么样的冒险(非常怀疑

冲突:

  • 如果没有实现返回聚合的另一层存储库,我们甚至无法部分解决上述陷阱

  • 通过实现额外的存储库层,我们将忽略EF的内置功能(每个DbSet都已经是仓库)并且使应用程序过于复杂


我的结论是:

请原谅我的无知,但基于以上信息-要么是实体框架 不足以用于域驱动设计,或者域驱动设计是不完善过时的方法。

我怀疑每种方法都有其优点,但是我现在已经完全迷失了,对如何将EF与DDD调和一无所知。


如果我错了-请问至少有人能详细介绍一下如何使用EF进行DDD的简单说明(甚至提供不错的代码示例)吗?


我根据对EF工作原理的了解在此详细说明了步骤。这些步骤仍然无法解决通过导航访问孩子的问题。属性或通过Db设置DbContext。
亚历克斯·赫尔曼

Answers:


8

DDD和EF之间几乎没有关系。

DDD是一个建模概念。这意味着要考虑领域,业务需求并对其进行建模。特别是在面向对象的上下文中,这意味着创建一个反映业务功能和能力的设计。

EF是一种持久性技术。它主要涉及数据和数据库记录。

这两个人离婚了。DDD设计可以在引擎盖下以某种形式使用EF,但两者不应以任何其他方式交互。

领域驱动设计的某些解释实际上确实提倡数据建模,而我认为这就是您的问题。在这种解释中,“实体”和“值对象”本质上仅是无功能的数据持有者,并且设计本身涉及这些实体所拥有的属性以及它们之间的关系。在这种情况下,DDD与EF可能会出现。

但是,这种解释是有缺陷的,我强烈建议完全忽略它。

结论:DDD和EF并不互斥,只要您进行适当的对象建模而不是数据建模,它们实际上是互不相关的。DDD对象不应以任何形式或形式成为EF工件。DDD实体应该不会是EF“实体”的例子。在某些与业务相关的功能内,DDD设计可能将EF与一些相关的数据对象一起使用,但是这些对象应始终隐藏在与业务相关的面向行为的界面下。


1
EF只是节省时间。EF的变化跟踪和持久性是EF的一大帮助。不幸的是,当前无法在配置级别定义聚合的形状。
帕维尔沃罗宁

6

将EF视为真正的EF,即数据访问库,其类型仅比原始ADO.NET强。我不建议使用EF实体类为您的域建模,就像我不建议使用原始DataSet或DataTable为域建模一样。

我确实知道EF是作为数据库访问和域建模之间的捷径出售的,但是这种方法本质上是有缺陷的,因为它解决了两个很大程度上不相关的问题。.NET中还有其他尝试使类执行一些完全不相关的操作(例如.NET Remoting),但效果并不理想。

使用POCO类执行DDD,并且不要让数据库模式驱动您的设计。将EF保留在存储库/持久性层中,并且不要让EF实体泄漏到外部。


5

实体框架使UoW和存储库(DbSet)开箱即用

没有。

实体框架抽象是在考虑ORM而不是DDD的情况下构建的。DbSet任何版本的Entity Framework中的抽象都远不及DDD存储库的简单性-更不用说DbContext它比UnitOfWork暴露了无数的东西。

这是EF Core 2.1的摘要中元素的非详尽列表DbSet<TEntity>,我们在DDD中不需要:

  • Attach(TEntity) 及其所有兄弟姐妹
  • Find(Object[])
  • Update(TEntity) 及其所有兄弟姐妹
  • 实施中 IQueryable

除了拖延不必要的依赖关系之外,这些方法还使通常会暴露非常简单的收集行为的存储库的意图难以理解。再加上泄漏的抽象是开发人员不断地将自己与EF过多耦合的一个长期诱惑,并威胁到关注分离。

底线:您必须将这些胖子包装成精简,精简的概念,然后猜测是什么,这意味着要引入额外的类。

一个可以使用EF和DDD进行操作的相对合理的示例(尽管所表达的某些观点值得商)):https : //kalele.io/blog-posts/modeling-aggregates-with-ddd-and-entity-framework/

其他人则决定生成额外的层,甚至经常通过将DbContext注入聚合根来违反SRP。

我真的看不到这句话的两个部分之间的联系。无论采用哪种方法,DDD中都有一个称为“应用程序服务”的东西,您可以在其中操作工作单元/存储库(或DbContext)。不在汇总根中。

如果这是一种受过教育的权衡,则可能是一种有效的方法,但是最近的反存储库“实体框架极简主义”趋势是一种幻想。当实体框架创建者并没有采取任何行动使他们的框架与最佳实践兼容时,它就将DDD模式归因于实体框架的冲突。一直以来,它们都与该框架紧密相连,并伴随着随之而来的代码安全性和可维护性方面的所有问题。


2

冲突:

如果不实现另一层返回聚合的存储库,我们甚至无法>部分解决上述陷阱

通过实现额外的存储库层,我们将忽略EF的内置功能(每个DbSet都已经是一个仓库),并使应用程序过于复杂

我使用了一种方法,其中每个聚合都拥有自己的DBContext,映射了聚合所需的内容。我认为Julie Lerman也对此进行了描述。

效果很好,但是对于那些您不想将概念链接到实体的更有趣的模型可能并不足够。



DBContext Per Aggregate方法有什么好处?这是用EF实施DDD的默认方法吗?
亚历克斯·赫尔曼

朱莉·勒曼(Julie Lerman)不是指每个有界上下文的DbContext吗?
Mvision

0

只想分享可能的解决方案以供考虑:

  1. 避免直接在服务层中引用EF项目

  2. 创建一个额外的存储库层(使用EF项目并返回汇总根)

  3. 参考服务层项目中的存储库层

建筑

  • 用户界面

  • 控制器层

  • 服务层

  • 储存库层

  • 实体框架

  • 核心项目(包含EF模型)


我看到这种方法的陷阱:

  • 如果存储库返回的聚合根不如EF模型树(例如,我们返回映射的对象)-我们将失去EF跟踪更改的能力

  • 如果Aggregate Root是EF模型- 即使我们无法处理(我们没有在服务层中引用EF项目),其所有导航属性仍然可用DbContext

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.