建议对实体进行领域对象映射的方法。

39

我目前正在参与一个项目,我们开始使用DDD方法构建应用程序。 现在,我们正在考虑使用Entity Framework 6的Code First来帮助我们进行数据持久化。我的问题是如何最好地处理我们的领域对象和EF实体之间的数据映射?

4个回答

56
为了保持应用程序和自己的理智,绝对不要从与持久性相关的问题(例如什么数据库,什么ORM等)开始编写DDD应用程序,并始终(是的,始终)将数据库作为开发的最后一步进行处理。
对域进行建模,以及除持久性之外的任何其他模型。使用存储库模式使应用程序与持久性分离。按照应用程序需要定义存储库接口,而不是与db访问方法绑定(这就是为什么稍后要实现持久性,因此您不会被诱惑将应用程序与持久性细节耦合在一起)。
为repo接口编写内存实现,这通常意味着在列表或字典上添加一个简单的包装器,因此编写非常快且更重要的是容易更改。使用它们来测试和开发应用程序。
接口稳定并且应用程序有效后,就可以编写持久性实现,其中可以使用您希望使用的任何东西。在您的情况下EF,然后是映射。
现在,这是高度主观的,没有正确或错误的方式,只有您喜欢做事的方式。
就我个人而言,我习惯使用备忘录,因此我从域对象获取备忘录,然后手动将其映射到(微)ORM实体。我手动执行的原因是因为我的备忘录包含值对象。如果我使用AutoMapper,我需要进行配置,本质上我会编写比手动编写更多的代码。
(2015年更新)
现在,我只需将对象Json化,然后使用特定的读取模型或将其直接存储在包含序列化对象的“Data”列中的读取模型中即可。我仅在非常特殊的情况下使用Mementos
根据您的领域对象和EF实体的外观,您可能可以使用automapper完成大部分映射。但是,测试存储库会更加困难。

这取决于你以何种方式实现它,找到适合自己风格并易于维护的方式,但是永远不要设计或修改您的域对象以使其更兼容或匹配ORM实体。这不是关于更改数据库或ORM,而是确保域(和应用程序的其余部分)与持久性详细信息(ORM是其中之一)正确解耦。

因此,抵制重用其他层次实现细节的诱惑。应用程序结构为层次结构的原因是您希望解耦。请保持这种状态。


谢谢你的回答。你说的“Json the object”是什么意思? - Artyom
@Artyom 将对象序列化为json。 - MikeSW

21

为什么你不直接使用EF实体作为领域对象呢?既然你正在考虑使用Entity Framework 6 code first,那么你应该首先设计领域模型,然后再设计数据库结构。

我一直在使用NHibernate,并相信在EF中你也可以指定从数据库表到POCO对象的映射规则,特别是在EF6中。开发另一个抽象层来覆盖EF实体需要额外的工作量。让ORM负责这个任务吧。

我不同意你可能读到的那篇文章"Entity Framework 5 with AutoMapper and Repository Pattern",以及还有很多其他人只是将EF实体简单地用作领域对象

AutoMapper肯定会在您开始构建表示层并面对大量UI特定视图模型时帮助您。它在构建贫血模型方面非常有用,但是在没有公共setter的真实域对象中有点无用。

Jimmy Bogard有一篇关于AutoMapper的旧文章"The case for two-way mapping in AutoMapper",他在其中说道:"我们从不需要双向映射,因此不存在双向映射。"

我們使用AutoMapper來做什麼? 我們的五個配置文件包括:
- 從Domain到ViewModel(MVC的強類型視圖模型) - 從Domain到EditModel(MVC表單的強類型視圖模型) - 從EditModel到CommandMessages - 從鬆散類型的EditModel到強類型、拆分的消息。一個EditModel可能會生成半打消息。 - 從Domain到ReportModel - 強類型的Telerik報告 - 從Domain到EDI Model - 用於生成EDI報告的扁平化模型

2
如果你的数据库已经存在,高级工程师想要将大量业务逻辑锁定在存储过程中,该怎么办呢?有什么建议可以解决这种情况吗?(找新工作?) - crush
@crush 如果“高级工程师想要将大量业务逻辑锁定在存储过程中”,那么我认为这些业务逻辑应该由(集成)测试覆盖。通常这样做是出于性能方面的考虑,所以可能有其原因。 - Ilya Palkin
1
我已经使用Dapper和AutoMapper实现了所有的存储库,但我希望让我的团队开始使用EF,并使用Code First开发新功能,以便他们可以看到优势,并希望摆脱存储过程(除非真正需要)。传统上,业务逻辑被锁定在存储过程中,因为高级开发人员似乎不太熟悉SQL之外的语言,而且不理解如何编写可重用的代码组件。我还强制要求我们使用Id而不是引用来作为聚合根。 - crush
你能在这里回答我的问题吗:https://dev59.com/Ahb7s4cB2Jgan1zn1La8? - w0051977
+1 对于“使用AutoMapper和Repository Pattern的Entity Framework 5”。我不同意它的内容。我只是一直在寻找那个解释。 - w0051977
显示剩余3条评论

6
由于我正在研究这个主题,我认为大多数使用或推荐使用EF POCO作为域对象的开发人员并不需要DDD。DDD是复杂域逻辑的推荐方法,当你拥有复杂的域逻辑时,你很可能没有将你的域逻辑与数据存储结构进行1:1映射。虽然在许多情况下,您将拥有与数据库设计类似结构的域对象,但随着您的领域变得更加复杂,您会发现许多领域实体需要偏离数据实际存储方式。如果您想要使用自动映射器,那么我的意见也是相同的 - 在这种情况下,您可能没有需要DDD。您的应用程序可能很简单,并且不会受益于DDD的复杂性。请注意,DDD不是架构或设计模式,而是一种使用普及语言构建软件以开发丰富域模型的方法。
我在一个企业规模系统上工作,其中一个示例是我们的文档业务逻辑。文档存在的条件包括:
- 分配给选项卡 - 根据标签默认值分配默认属性 - 选项卡可能已经存在,也可能不存在,选项卡还具有全局定义的默认属性 - 文档可能有图像(文件),也可能没有 - 我们的系统具有模块(跟踪与成像),因此客户可能甚至无法访问存储文件 - 必须进行审计日志记录 - 必须跟踪文档历史记录 - 还有许多规则
因此,一个简单的文档概念不仅涉及到大量的数据库表数据,还需要很多业务逻辑才能创建。这种情况下不存在1:1映射,因为我们的系统还有必须分离的特性模块,所以我们通过继承和装饰者模式实现了不同类型的文档业务实体。
我们还拥有约200个数据库表的数据库。我们必须维护一个非常干净和优化的数据库。不可能根据数据库或其他方面做出业务逻辑决策。它们必须是分开的,以便我们可以根据各自的需求在自己的光芒下维护每个。
你面临的挑战是一个复杂的问题 - 建立软件并不是一项微不足道的任务,而且不幸的是,业务逻辑和“数据”实际上是一样的 - 你不能没有另一个。实际上,为了构建软件,我们必须以一种有意义的方式处理这个问题,既要考虑特性,也要考虑性能。
所有这些的挑战在于软件有如此多的需求。那么如何拥有丰富的域模型,使用像EF这样的ORM,并且能够解决不适合您的域模型的数据查询等问题?而且如何以使您的代码库不会出现问题的方式实现这一点?
在使用EF等技术时,主要考虑的是如何从数据(EF实体)中轻松创建域对象,并在工作单元模式、上下文、更改跟踪等方面进行附属。对我来说,这看起来像一个具有访问由上下文追踪的实体的域对象,在工作单元内。这意味着您可以加载单个实体到一个域对象中,或者在单个查询表达式中加载多个实体以提高性能,并仍然使用EF进行插入和更新的更改跟踪。
我的第一种方法是使用Active Record解决问题,这与EF非常搭配。每个域实体都有自己的上下文,并管理创建、删除和更新的所有业务逻辑。对于90%的软件来说,这很棒,对性能没有任何问题。我们使用具有自己上下文的域服务来处理高级查询方案。我无法在此描述我们的整个方法,因为它需要一个博客来介绍,但是…
我现在也正在研究一种不使用Active Record的方法。我认为存储库很关键,可以加载多个并处理查询方案,可能还包括规范。
我的观点是,如果您真正需要DDD,则数据层和域层应完全分开,如果您不这样认为,那么可能不需要DDD。

4

哦,我不会额外增加那个层。

NHibernate和Entity Framework Code-First(我会用EF)的设计目的就是解决这个确切的问题 - 将您的领域对象映射到关系模型(其设计上没有相同的约束,因此可能,而且很可能,具有不同的形状)。

看起来很可惜浪费了EF的出色映射能力,用其他东西替换它,即使是AutoMapper。


21
我完全不同意这种说法。任何 ORM 都只是在关系型数据库之上提供了一个“虚假”的面向对象数据库。它与领域对象映射没有任何关系,只与数据有关。将你的领域对象绑定到 ORM 实体是目前最大的反模式之一,会导致代码复杂化,并且当 ORM 成为应用程序的支柱时会带来很多头痛。仓储模式的目的就是将领域与持久性解耦。只有当你的应用程序是 CRUD 时才可以直接使用 ORM。ORM 的作用是将表映射到 POCO 类,而不仅仅是如此。 - MikeSW
6
我不确定我们对仓储模式的定义是否相同。当然,仓储模式为数据访问提供了一个抽象层,但这里的重点是返回哪些对象。您建议查询数据库,使用ORM映射到对象,然后再使用另一个映射工具将这些对象映射到更多的对象?也许我应该明确指出EF Code First,在这种情况下,非常重要的想法是先编写域对象,然后编写一组映射来将它们持久化到数据库中。 - Neil Barnwell
13
该仓库始终返回应用程序上下文(在本例中为领域)对象。EF Code First只是营销说辞,没有ORM可以执行魔法,一个人几乎必须修改领域对象以满足ORM的需要(虚拟属性、必备设置器等)。你要么建模业务概念,要么服务于ORM,你真的无法同时很好地做到两者。更糟糕的是,许多开发人员在对领域进行建模时仍然使用关系型思维方式(一对多、多对多),而ORM支持(我认为要求)这种思维方式。首先编写领域对象不仅仅是技术步骤,它意味着你不在乎数据库。 - MikeSW
5
使用Code First时,由于映射关系位于单独的类中,因此不会实际上与ORM耦合,所以领域对象仍然不知道持久性。我漏掉了什么? - Neil Barnwell
6
请参考上文: 必需的属性设置器。这不是什么大事,但小问题会悄然而至,我为什么要关心ORM是如何定义领域模型的呢?同样的情况也适用于nosql数据库。当在将领域映射到表时出现问题时,人性就会发挥作用,并开始调整领域模型以使其更兼容ORM。如果开发人员缺乏坚定的自律精神来保持解耦合并屈服于像“每个人”那样使用银弹,则一切都可能出错。此外,所有EFCF示例都显示数据结构,而不是真实的领域对象。 - MikeSW
显示剩余14条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接