富域模型和 ORM

Posted

技术标签:

【中文标题】富域模型和 ORM【英文标题】:Rich Domain Model and ORM 【发布时间】:2012-12-31 13:52:19 【问题描述】:

Martin Fowler 将贫血域模型视为一种反模式。

由于Object Relational Impedence Missmatch,将持久性模型滚动为域模型似乎也严重偏离了。为了持久性和规范化,我们倾向于将类分解为非常小的部分,在这些类之上使用方法是愚蠢的。加上持久性很少改变,但业务逻辑变化相当大。

所以我们需要一个建立在持久性模型上的 DomainModel(而不是一模一样)。然后,此领域模型将包含业务逻辑属性和方法。

但现在这些领域模型仍然在服务背后,为了将它们暴露给外部世界,我们需要将它们转换为 DTO。

我们在这里做了很多映射。

    领域模型的持久性 将域模型转换为 DTO 以在服务之间传递

它并没有就此结束,因为 DTO 可能需要映射到 ViewModel。

所有这些以及重复验证逻辑的问题仍然没有消失,因为客户端需要实时验证。 ViewModel 对验证一无所知,因此例如在 SPA 中,您不得不在客户端(通常在 javascript 中)再次重写验证逻辑。

此外,服务本质上是无状态的(消息或面向 RPC),所以我们正在做所有这些映射,从持久性到 OO,然后再回到过程,有什么好处?对于大多数 IT 预算,您如何证明成本的实际价值?

我知道拥有完整的 DDD、聚合根、域模型等是多么“酷”,但你如何证明成本和对开发效率的影响是合理的?

反模式(或反模式)是一种用于社交或商业的模式 可能常用的操作或软件工程,但 在实践中无效和/或适得其反

如果是这样,那么 DDD 和富域模型是否比“精益”域模型更适合上面的反模式定义。对不起,我鄙视加载的词,“贫血”。

通过保持领域模型,“精益”实际上允许共享它而不违反“抽象依赖原则”、“不要重复自己”以及映射一个数据载体的耗时、乏味和容易出错的过程到另一个,以及任何相关的单元测试(除非您正在考虑进行不带单元测试的映射并希望获得最好的结果)。

【问题讨论】:

【参考方案1】:

首先,我不认为您可以真正轻松地摆脱在客户端服务器上重复验证逻辑。但是,这不仅限于 DDD。有一些机制可以缓解疼痛,但总是需要付出一些努力。

另一部分是整个地图业务:)

您正在做的是有效地用于执行读取。您可能认为您需要阅读您的实体才能对其进行编辑。如果您在实体对象上执行基于实体(此处可能更多地用数据库术语表示实体 - 整个记录)的操作,而不是基于任务的操作,这是正确的。一个愚蠢的例子可能是客户打电话到呼叫中心更改地址。接线员调出客户记录并编辑地址。这是基于实体的,会导致典型的问题 w.r.t.并发,因为可能对同一记录执行 2 个操作(但不太可能)。这是一种非常传统的 UX 设计方法:“编辑记录”。

将此与屏幕上的一个按钮进行对比,上面写着:“更改地址”。您只需更改记录上的地址,虽然这看起来是一回事,但实际上却大不相同。两次更改同一地址的操作比更改同一记录的机会要小得多。如果需要,可以在这部分进行并发检查。

现在,如果一个人不读域,他会读什么。数据从哪里来。这就是 CQRS(命令/查询职责分离)的用武之地。过去它与事件溯源混淆/结合,但这不是必需的。您可以为您的应用程序创建一个简单的查询端,专注于返回您需要的数据。在 C# 中,如果是单个实例,我使用 DataRow,对于多个实例,我使用 DataTable,对于更复杂的情况,我使用自定义 DTO。可能有一种方法甚至可以摆脱匿名类型(尚未对此进行调查)。

因此:

领域模型 = 操作 / 计算 / 写入 查询服务 = 阅读

在某些情况下,您可以简单地加载实体/聚合,例如在 Web 应用程序中,因为它知道(或可能知道)您的域模型,但智能客户端会有点反模式。

理由相当棘手,但归结为维护。如果您的方法没有减轻您的维护负担,那么很可能是某些东西没有正确应用,需要进行一些重构。

DDD 不仅是关于技术实现,尽管它还有很长的路要走,朝着正确的 OO 建模方向发展。我猜其他想法无论如何都会输入到软件中,所以无论如何软件似乎都是焦点。我们都想看看橡胶与道路的交汇点。

就像大多数 DDD 做错的事情一样 :)

【讨论】:

【参考方案2】:

tl;dr 域模型没有很好地定义,它可能在设计时考虑了以数据库为中心的方法。

DDD 的主要目的是在代码中对业务概念和流程进行建模。我真的怀疑您的业务概念和流程只是一堆属性。但如果它们真的是,那么领域模型可以与持久性模型相同,因此您不必进行任何映射。

持久性模型模拟对象状态的存储方式。如果您不在数据库领域,那么领域和持久性模型就不能有相同的目的。我在 DDD 中看到的最大错误之一是认为域模型仍然是数据驱动的。在 DDD 中,您没有具体的数据库。你有存储库。没有一对多、多对多等关系。没有表格,也没有行。只有对象试图尽可能准确地表示域。

简单地说,Domain 关心建模业务行为,而 Persistence 关心存储对象状态。我在这里看到了两种不同的责任。

关于验证,您有两种类型:输入数据格式的验证,然后根据您在对象状态中更改的内容验证其他业务规则。

关于重复,我认为您指的是输入格式,但就像@EbenRoux 所说的那样,有一些机制可以帮助解决这个问题。在 asp.net mvc 中,大多数验证规则也包含 js 版本。

让我告诉你一个关于服务的小秘密。虽然它们的接口可以在域中定义,但它们的实现可以位于持久层中,从而允许它们直接使用存储。而且由于应用程序的其余部分以抽象形式(接口)使用服务,因此只有 DI 容器会知道肮脏的秘密。

DDD 不是要酷,而是要根据领域设计应用程序。我敢打赌,很少有人会开发一个应用程序,其唯一目的是成为数据库的 UI。大多数人旨在通过他们的软件提供服务,构建解决问题的虚拟产品。 将应用程序设计为驱动您想要解决的问题而不是由您碰巧使用的技术工具是有道理的。

这听起来怎么样,你想要一栋砖房,但建造者说:“对不起,我的锯子只适用于木头”。好吧,不要使用锯子,使用其他可以帮助切割砖块的工具。工具需要针对问题进行调整,反之亦然。

【讨论】:

那么DDD中的Domain是有状态的还是无状态的?那么域是否应该公开其属性?如果不是,域方法的参数和返回类型是什么?,DTO?如果它确实暴露了它的属性,这怎么比贫血模型更好? 域主要是关于行为的。当然,域对象可以具有属性,但它不仅具有属性。这些属性不仅是基元。如何将 IFormattedContent 直接保存在列中?我开始认为您当前的域只是使用 DDD 术语的数据驱动程序代码的集合。 @Alwyn 域的一部分是有状态的——这正是实体的关键点,即拥有状态和身份,以便我们可以跟踪该状态的变化。另一方面,值对象不是有状态的(如果你愿意的话,状态冻结)。域服务显然应该是无状态的。【参考方案3】:

您似乎混淆了很多概念,将富域模型方法归咎于它不直接负责的事情。

富域模型与分层架构正交,尤其是富域模型并不决定您拥有的层数、这些层之间应交换的数据结构以及应如何映射它们。

富域模型与验证正交,除了后端验证之外,还没有说明需要客户端检查。

换句话说,使您的域模型与服务中的所有业务逻辑无关,不一定会让您免于编写大量样板 DTO 映射代码,也不会消除对客户端“双重检查”的需要(即顺便说一下,这是一种普遍接受的最佳做法)。

这并不意味着您关于成熟的多层架构的成本和重量的观点是无效的。您可能会对 Mark Seemann 讨论类似问题的帖子感兴趣:http://blog.ploeh.dk/2012/02/09/IsLayeringWorthTheMapping.aspx

【讨论】:

我认为你链接的文章总结了这个困境。除了我不相信通过网络发送域模型,这似乎是 DTO 的工作。在 DTO 上进行行为并将其暴露给客户端是没有意义的。所以对我来说,除了他提到的 3 个抽象层和映射之外,该架构只能“可行”。那是很多映射!不是说为了重用而将 UI 关注点引入域的另一种方式也是正确的。我认为在两个极端之间有一个“更快乐”的媒介。 “移动更少的数据,事情可能会变得更简单”——他自己在文章结尾的结论,对我来说这听起来像是精益模型。 “在 DTO 上有行为并将其暴露给客户端是没有意义的” - 现在我了解您在丰富/贫乏的域模型和分层之间建立的联系。您是否暗示应该从尽可能多的业务逻辑中剥离域对象,以便将它们直接发送到 UI 层而不需要 DTO?还是您所说的“精益模型”? 不,它可能会起作用,我只是认为您需要在域模型和 DTO 之间再增加一层。该链接中的一位评论员也发表了同样的看法。我可以看到这个工作,它只是很多开销,我不知道权衡是否值得。

以上是关于富域模型和 ORM的主要内容,如果未能解决你的问题,请参考以下文章

领域模型和实体框架之间的存储库模式和映射

047:创建和映射ORM模型

ORM模型简介

Django 模型系统(model)&ORM--基础

laravel框架之ORM模型

– 模型和ORM基础