ORM是否启用富域模型的创建?


21

在我的大多数项目中使用Hibernate大约8年之后,我进入了一家不鼓励使用Hibernate并希望应用程序仅通过存储过程与DB进行交互的公司。

在执行了几周之后,我无法为我将开始构建的应用程序创建丰富的域模型,并且该应用程序看起来像(可怕的)事务脚本。

我发现的一些问题是:

  • 由于存储过程仅加载最少的数据,因此无法导航对象图,这意味着有时我们具有具有不同字段的相似对象。一个示例是:我们有一个存储过程来从客户那里检索所有数据,另一个存储过程是从客户那里检索帐户信息以及一些字段。
  • 许多逻辑最终出现在帮助器类中,因此代码变得更加结构化(实体用作旧的C结构)。
  • 令人讨厌的脚手架代码,因为没有框架可以从存储过程中提取结果集并将其放入实体中。

我的问题是:

  • 有没有人遇到过类似情况,并且不同意存储过程方法?你做了什么?
  • 使用存储过程有实际好处吗?除了“没有人可以发布弃用表”这一愚蠢的观点之外。
  • 有没有一种使用存储过程来创建富域的方法?我知道有可能使用AOP将DAO /存储库注入到实体中,从而能够导航对象图。我不喜欢这个选项,因为它非常接近伏都教。

结论

首先,谢谢大家的回答。我得出的结论是,ORM无法启用Rich Domain模型的创建(如某些人所述),但是它确实简化了(通常是重复的)工作量。以下是对结论的更详细说明,但并不基于任何硬数据。

大多数应用程序请求并将信息发送到其他系统。为此,我们在模型术语(例如,业务事件)中创建抽象,然后域模型发送或接收事件。事件通常需要来自模型的一小部分信息,而不是整个模型。例如,在一家网上商店中,支付网关会向用户收取一些用户信息和总计费用,但不需要购买历史记录,可用产品和所有客户群。因此,该事件具有少量特定的数据集。

如果我们将应用程序的数据库作为外部系统,则需要创建一个抽象,以允许我们将域模型实体映射到数据库(如NimChimpsky所述,使用数据映射器)。明显的区别是,现在我们需要为每个模型实体手工映射到数据库(旧模式或存储过程),而且还有一个痛苦,因为两个实体不同步,所以一个域实体可能会部分映射数据库实体(例如,仅包含用户名和密码的UserCredentials类映射到具有其他列的Users表),或者一个域模型实体可能映射到多个数据库实体(例如,如果存在一对一一张表上的映射,但我们只想将所有数据归为一类)。

在只有几个实体的应用程序中,如果不需要横向实体,那么额外的工作量可能很小,但是当有条件需要横向实体时,额外的工作量就会增加(因此,我们可能想实现某种“懒惰”正在加载”)。随着应用程序具有更多实体的发展,这项工作只会增加(我感觉它会非线性地增加)。我在这里的假设是,我们不会尝试重塑ORM。

将数据库视为外部系统的一个好处是,我们可以围绕以下情况编写代码:需要运行两种不同版本的应用程序,其中每个应用程序都有不同的映射。在连续交付生产的情况下,这变得更加有趣……但是我认为ORM在较小程度上也是可行的。

我将不考虑安全性,因为开发人员即使没有访问数据库的权限,也可以通过注入恶意代码来获取存储在系统中的大部分(即使不是全部)信息。我不敢相信我忘了删除记录客户信用卡详细信息的行,亲爱的上帝!)。


小更新(6/6/2012)

存储过程(至少在Oracle中)阻止执行诸如零停机时间连续交付之类的操作,因为对表结构的任何更改都将使过程和触发器无效。因此,在更新数据库期间,应用程序也将关闭。Oracle 为这种称为基于版本的重新定义提供了解决方案,但是我向该功能询问过的少数DBA提到,该功能实施得很差,他们不会将其放入生产数据库中。


好吧,很明显,您可以执行Hibernate的操作,并使用继承来生成动态代理对象,从而使您可以检索对象图。尽管使用SP还是很骇客的:D
2012年

因此,如果没有休眠团队10多年的经验,我最终会重新投入休眠的一半:)。
奥古斯托

1
任何DBA都应防止某些用户丢弃特定的表。尝试执行此操作无关紧要。
JeffO 2012年

1
您可能会看看Mybatis-它可能会提供您所需的功能。它比映射框架少一个ORM。您可以根据自己的喜好编写SQL,并告诉Mybatis将其放在对象模型上的位置。它将处理带有多个查询的大型对象图,这听起来像您遇到的情况(许多精简存储过程)。
Michael K

1
@Augusto:我也遇到过类似的情况,这不是由于使用SP,而是由于使用了不支持对象关系的专有映射框架。我们花了几天的时间编写可以使用适当的ORM在几分钟内编写的代码。我从来没有解决过这个问题。
凯文·克莱恩

Answers:


16

您的应用程序仍应根据领域驱动的设计原则进行建模。是否使用ORM,直接JDBC,调用SP(或其他任何方法)都无关紧要。希望在这种情况下,从SP提取模型的薄层应该可以解决问题。正如另一位发帖人所述,您应该将SP及其结果视为服务,并将结果映射到您的域模型。


Martijn,我同意应使用DDD原理对应用程序进行建模,但是我面临的问题(请告诉我是否有解决方案!!)是某些存储的proc返回很少的信息来实例化DDD实体。请参阅此注释,我在其中详细解释了存储过程返回的信息。我可以通过调用多个存储的proc来避免这种情况,例如,检索所有用户详细信息,然后调用另一个来检索所有帐户信息,但是感觉很不妙:)。
2012年

1
@Augusto好吧...您是应用程序开发人员,因此您必须确定将某些字段设置为NULL对于存在某些对象是否有意义。如果有意义(例如对于某些任务),那就顺其自然。如果不是,请要求SP的作者提供更多数据,以便您可以创建对象。
Jacek Prucia 2012年

并添加到Jacek的评论中-调用2个以上存储的proc实际上是完全可以接受的,再次将它们视为要创建域模型必须调用的两个远程服务,这没什么大不了:-)。
Martijn Verburg 2012年

@Martijn:根据我的经验,仅薄薄一层是不够的。映射代码可能比基础业务逻辑更长。
凯文·克莱恩

@Kevin cline-好点,在答案中加上“希望” :-)
Martijn Verburg,2012年

5

使用存储过程有实际好处吗?

在金融界(以及需要遵守萨班斯-奥克斯利法案的地方),您需要能够审计系统以确保它们能够完成应有的工作。在这些情况下,通过存储过程进行所有数据访问时,确保合规性要容易得多。而且,当所有临时SQL都删除后,隐藏起来就困难得多。有关为什么这将是一件“好事”的示例,请参考Ken Thompson的经典论文《对信任的思考》


是一百万次是!您还需要确保用户不能做他们不应该做的任何事情,包括没有对表的直接权限以及存储的proc会极大地帮助您。
HLGEM 2012年

1
我在一家上市公司工作,所以我们非常SOX。这可能是我对审计的知识较差,但是我看不出在数据库级别(通过存储的proc)或在应用程序级别进行审计之间的区别。每个应用程序应具有自己的数据库模式,并且只能从该应用程序访问该模式,而不能在不同的应用程序之间共享。
奥古斯托

断开的链接...
Alex R

@AlexR,固定链接
Tangurena

2

存储过程比客户端SQL代码更有效。他们在数据库中预编译了SQL,这也使其可以执行一些优化。

在体系结构上,SP将返回任务所需的最少数据,这很好,因为这意味着传输的数据较少。如果您具有这样的体系结构,则需要将DB视为服务(将其视为Web服务,每个SP都是一种调用方法)。像这样使用它应该不是问题,而ORM会指导您像处理本地数据一样使用远程数据,因此如果您不小心,就会诱使您引入性能问题。

我曾经遇到过完全使用SP的情况,DB提供了数据API并使用了它。该特定应用程序的规模非常大,并且表现出色。在那之后,我对SP不会再说坏话了!

还有另一个优势-DBA将为您编写所有SQL查询,并愉快地处理数据库中的所有关系层次结构,因此您不必这样做。


3
gbjbaanb,您所说的大部分内容都适用于较旧的数据库。大多数较新的数据库都经常重新编译查询,以决定要使用哪些新优化(即使它们在存储的产品中也是如此)。我同意您所说的将数据库用作外部系统的说法,但是我也看到大量的工作,因为应用程序拥有数据库,并且两者应尽可能保持同步。例如,以表/类和字段/列的命名。同样,让DBA编写过程的方法闻起来像是开发孤岛,而不是拥有一支跨学科的团队。
奥古斯托

7
SP不一定总是效率更高,我认为将SQL交给DBA是一个不好的方法。作为领域专家,开发人员应该知道他们想要获取什么数据以及如何获取它们
Martijn Verburg'Mar 3''21 '40

1
这是一个很好的答案,但是根据我的经验,大多数客户实际上不需要通过存储过程来控制数据访问而获得性能上的提升,而在应用程序层上充分利用ORM工具带来的不便。我经常在软件商店中看到这些体系结构决策,他们需要证明没有其他技能的“灰胡子”存储过程程序员的the肿工资是合理的。
maple_shaft

1
@Augusto the approach of letting the DBAs write the procedures smells like development silos为您提供了100个互联网,以了解这个真理。我一直将这种情况视为通过存储过程控制数据访问的情况。
maple_shaft

1
@maple_shaft:为什么编写SP的DBA不被视为开发人员团队的一部分?在可行的地方,他们是专业的编码人员,他们非常了解系统的这一方面,比大多数通用开发人员要好得多。这可能是导致ORM普及的问题。我的意思是,没有人会三思而后行让设计人员来设计GUI,那么为什么讨厌数据架构师来做架构呢?
gbjbaanb 2012年

2

经常发生的是,开发人员错误地将其ORM对象用作域模型。

这是不正确的,并将您的域直接绑定到您的数据库架构。

真正应该拥有的是您想要的丰富的独立域模型,并分别使用ORM层。

这意味着您将需要在每组对象之间进行映射。


1
这是一个好主意,但对于较小的项目,它确实开始感觉像是过大了。此方法还需要ORM持久层和域模型之间的转换层。
maple_shaft

@maple_shaft同意,这就是我所说的“映射” :-)的意思
ozz 2012年

@Ozz,我的工作方式就是,实体类是域模型(并且我可能会添加很多成功的东西)。我同意它将域模型与架构联系起来,但这正是我想要的,因为我在配置中使用约定,而且副作用是,如果我看到实体上的字段,则无需费劲有关存储该信息的表和列的名称。
奥古斯托

@Augusto我也做到了!正如maple_shaft所说,对于小型CRUD风格的应用程序来说很好,但是OP发现了许多问题。一个示例可能是您有一个多对多映射表,例如:StudentClasses,将Student映射到他们的班级,并且只包含StudentID和classID,您不一定要在您的域中映射它。这只是我的头等例子的快速入门。
ozz 2012年

2
@Ozz:您的评论似乎与ORM的想法相矛盾。ORM不会“将您的域直接绑定到数据库架构”。ORM 您的域映射到数据库模式,而无需单独的DAO层。这就是ORM的重点。而且大多数ORM都可以很好地处理多对多映射,而映射表不需要域模型。
凯文·克莱恩

1

可以填充您的域对象,但是,请不要使用Hibernate。我认为恰当的术语是data-mapper。您保留的数据很有可能与域对象的结构完全不同。


我们目前正在使用数据映射器,但是问题在于它们存储的proc返回的数据最少,有时这不足以填充对象(也许我们应该允许存储过程返回更多的信息)。例如,一个存储过程可能返回用户电子邮件,名字,姓氏;而另一个添加用户ID和地址。由于数据不同,因此我们使用不同的对象来存储数据,这意味着我们具有不同的“用户”类。我试图避免在这里使用继承,因为我认为这是错误的使用。
2012年


@Karmii,我认为接口在这里没有帮助,因为那时我们需要在不同的类中复制逻辑。或者,我们可以使用的接口,然后委托处理一个辅助类,但这不是真正OO :(。
奥古斯托

1
@Augusto我不明白问题所在:“存储的proc返回的数据最少,有时不足以填充对象”,因此您更改sproc或创建另一个sproc,然后让数据映射器进行映射
NimChimpsky 2012年
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.