为什么所有Active Record都讨厌?[关闭]


103

随着我越来越了解OOP,并开始实现各种设计模式,我不断回到人们讨厌Active Record的情况

人们常常说它的伸缩性不好(以Twitter为例),但实际上没有人解释为什么伸缩性不好。和/或如何在没有缺点的情况下实现AR的优点(通过类似但不同的模式?)

希望这不会成为关于设计模式的一场圣战-我只想特别地了解一下Active Record的问题。

如果扩展性不好,为什么不呢?

还有什么其他问题?


9
我猜一般来说,人们对设计模式的憎恨和不喜欢与错误使用有关。人们倾向于过度使用并在错误的上下文中使用它们,最终得到的解决方案比原始解决方案
terjetyl

1
Ruby的Active Record实现更像是ORM。
Jimmy T.

1
有一种社会现象是为了获得欣赏,更多的认可,似乎更聪明和更鲜为人知的边缘,人们倾向于机械地重复进行任何对当前标准,模型,广泛采用的技术的否定性的炒作,将其与革命性进展进入下一波。
安德烈·菲盖雷多

Answers:


90

ActiveRecord设计模式ActiveRecord Rails ORM库,还有.NET和其他语言的大量仿制品。

这些都是不同的东西。他们大多遵循该设计模式,但是以许多不同的方式对其进行扩展和修改,因此在有人说“ ActiveRecord很烂”之前,需要先说“哪个ActiveRecord上有堆?”来进行限定。

我只熟悉Rails的ActiveRecord,我将尝试解决所有因使用它而引起的投诉。

@布莱姆

我看到的Active Records问题是,它总是只有一张桌子

码:

class Person
    belongs_to :company
end
people = Person.find(:all, :include => :company )

这会使用生成SQL LEFT JOIN companies on companies.id = person.company_id,并自动生成关联的Company对象,因此您可以执行此操作,people.first.company并且不需要访问数据库,因为数据已经存在。

@ pix0r

Active Record的固有问题是数据库查询会自动生成并执行以填充对象并修改数据库记录

码:

person = Person.find_by_sql("giant complicated sql query")

不建议这样做,因为它很丑陋,但是对于您只需要简单地编写原始SQL的情况,就很容易做到。

@蒂姆·沙利文

...然后选择模型的几个实例,基本上就是在做“ select * from ...”

码:

people = Person.find(:all, :select=>'name, id')

这只会从数据库中选择名称和ID列,映射对象中的所有其他“属性”都将为nil,除非您手动重新加载该对象,依此类推。


厉害!我不知道该特定功能。另一个赞成AR的论点使我进入了我的武器库。
蒂姆·沙利文

加入超越了Active Record模式。
Jimmy T.

“ Person.find_by_sql”根本不是活动记录模式。它的“活动记录”几乎使我失败了,所以我需要手动对其进行修补。
magallanes

52

我一直发现ActiveRecord适用于Model相对平坦(例如,没有很多类层次结构)的基于CRUD的快速应用程序。但是,对于具有复杂OO层次结构的应用程序,DataMapper可能是更好的解决方案。尽管ActiveRecord假定表和数据对象之间的比例为1:1,但这种关系在更复杂的域中变得难以处理。在关于模式的书中,马丁·福勒(Martin Fowler)指出,ActiveRecord在模型非常复杂的情况下会崩溃,并建议使用DataMapper作为替代方案。

我发现这在实践中是正确的。在您的域中具有大量继承的情况下,将继承映射到RDBMS比映射关联或组合更加困难。

我这样做的方法是让控制器通过这些DataMapper(或“服务层”)类访问“域”对象。这些不直接镜像数据库,而是充当某些实际对象的OO表示。假设您的域中有一个User类,并且需要具有对其他对象的引用或其他对象的集合,这些对象在检索该User对象时已经加载。数据可能来自许多不同的表,而ActiveRecord模式可能会使它变得非常困难。

例如,您的控制器代码不是通过直接加载User对象并使用ActiveRecord样式的API访问数据,而是通过调用UserMapper.getUser()方法的API来检索User对象。正是那个映射器负责从它们各自的表中加载任何关联的对象,并将完成的用户“域”对象返回给调用者。

本质上,您只是添加了另一层抽象以使代码更易于管理。您的DataMapper类是否包含原始的自定义SQL,还是对数据抽象层API的调用,甚至是自己访问ActiveRecord模式,都与接收良好的,填充的User对象的控制器代码无关紧要。

无论如何,这就是我的方法。


5
听起来您根本不熟悉ActiveRecord。“ ActiveRecord假定表之间的比例为1:1”。根本不是真的。ActiveRecord具有各种超酷的关系魔术。参见api.rubyonrails.org/classes/ActiveRecord/Associations/…– tybro0103 2011
6

16
@JoãoBragança-也许不是讽刺的评论,您实际上可以解释在分割一个人的数据时会遇到的困难-这样我们其余的人都可以学到一些东西:)
Taryn East

11

我认为人们为什么在ActiveRecord上“讨厌”与它有什么“错”之间可能有非常不同的原因。

在仇恨问题上,对Rails相关的任何事物都有很多毒液。至于什么地方出了问题,很可能就像所有技术一样,在某些情况下它是一个不错的选择,在某些情况下还有更好的选择。根据我的经验,您无法利用Rails ActiveRecord的大多数功能的情况是数据库的结构不良。如果您访问的数据没有主键,并且违反了第一种标准格式,即需要大量存储过程来访问数据,那么最好使用一些SQL包装程序。如果您的数据库结构相对良好,则ActiveRecord可以让您利用它。

要添加主题以回复带有代码段重新连接的A​​ctiveRecord困难的评论者的主题

@Sam McAfee假设您的域中有一个User类,并且需要具有在检索该User对象时已经加载的对其他对象的引用或集合。数据可能来自许多不同的表,而ActiveRecord模式可能会使它变得非常困难。

user = User.find(id, :include => ["posts", "comments"])
first_post = user.posts.first
first_comment = user.comments.first

通过使用include选项,ActiveRecord使您可以覆盖默认的延迟加载行为。


8

我的回答很长而且很晚,甚至还不完整,但是很好的解释为什么我讨厌这种模式,观点甚至情绪:

1)简短版本:Active Record 在数据库和应用程序代码之间创建“ 强绑定 ” 的“ 薄层 ” 。这根本解决不了逻辑,没有任何问题,也没有任何问题。恕我直言,它不提供任何值,只是为程序员提供了一些语法糖(然后它可以使用“对象语法”来访问关系数据库中存在的某些数据)。为使程序员感到舒适的工作(IMHO ...)最好投入到低级数据库访问工具上,例如,简单,简单,简单和类似方法的一些变体(当然,概念和高雅程度会随着使用的语言)。hash_map get_record( string id_value, string table_name, string id_column_name="id" )

2)长版本:在任何具有“概念控制”功能的数据库驱动项目中,我都避免使用AR,这很好。我通常会构建一个分层的体系结构(您迟早会将您的软件分成多个层,至少在大中型项目中):

A1)数据库本身,表,关系,甚至DBMS允许的逻辑(MySQL也已经成长)

A2)通常,除了数据存储之外,还有很多其他东西:文件系统(数据库中的斑点并不总是一个好的决定……),旧系统(想象自己“如何”访问它们,可能有多种选择)。不是重点...)

B)数据库访问层(在此级别上,非常欢迎使用工具方法,轻松访问数据库中的数据的助手,但是AR在这里没有提供任何价值,除了一些语法糖之外)

C)应用程序对象层:“应用程序对象”有时是数据库中表的简单行,但无论如何大多数情况下它们都是复合对象,并且附加了一些较高的逻辑,因此在此级别上花费时间在AR对象上显然是毫无用处的,这浪费了宝贵的编码时间,因为无论如何,无论有无AR,这些对象的“真实价值”和“更高逻辑”都需要在AR对象之上实现!并且,例如,为什么要抽象一个“日志条目对象”?应用逻辑代码会编写它们,但是应该能够更新或删除它们吗?听起来很傻,而且App::Log("I am a log message")比使用起来容易一些le=new LogEntry(); le.time=now(); le.text="I am a log message"; le.Insert();。例如:在应用程序的日志视图中使用“日志条目对象”可处理100、1000甚至10000条日志行,但迟早您将不得不进行优化-我敢打赌,在大多数情况下,您只会在您的应用程序逻辑中使用该漂亮的小型SQL SELECT语句(这完全破坏了AR的构想。),而不是用大量的代码包装和隐藏将其包装在固定的AR构架中。您可能浪费在编写和/或构建AR代码上的时间花在了一个更聪明的界面上,以读取日志条目列表(很多方面,其中没有限制)。编码人员应该敢于发明新的抽象来实现适合预期应用的应用逻辑,而不是愚蠢地重新实现愚蠢的模式,一见钟情!

D)应用程序逻辑-实现交互对象以及创建,删除和列出(!)应用程序逻辑对象的逻辑(否,那些任务应该很少锚定在应用程序逻辑对象本身中:您桌上的纸迹是否说明了您办公室中所有其他工作表的名称和位置吗?忘记了列出对象的“静态”方法,这很愚蠢,这是一种糟糕的折衷方案,使人类的思维方式适合[某些并非全部AR框架] -] AR思维)

E)用户界面-好吧,我将在以下几行中写的内容非常非常非常主观,但是以我的经验,基于AR的项目经常忽略了应用程序的UI部分-在创建晦涩的抽象上浪费了时间。最终,这样的应用程序浪费了很多编码人员的时间,感觉就像来自编码人员的应用程序一样,内部和外部都倾向于技术。编码人员感觉良好(根据纸上的概念,终于完成了艰苦的工作,完成了所有工作,并纠正了这些错误……),并且客户“只需要了解它需要那样”,因为那是“专业”。好的,对不起,我离题了;-)

好吧,诚然,所有这些都是主观的,但这是我的经验(不包括Ruby on Rails,它可能有所不同,并且我对该方法的实践经验为零)。

在付费项目中,我经常听到需求,从创建一些“活动记录”对象开始,这是高层应用程序逻辑的基础。根据我的经验,这很明显客户(大多数情况下是软件开发公司)没有很好的概念,全面的看法以及对最终产品的概述的某种借口。这些客户认为框架僵化(“十年前在项目中运作良好。”),他们可能充实实体,定义实体关系,分解数据关系并定义基本的应用程序逻辑,但随后停下来并将其交给您,并认为这就是您所需要的...他们通常缺乏完整的应用程序逻辑,用户界面,可用性等概念,等等...他们缺乏广阔的视野,也缺乏对细节,他们希望您遵循AR的方式,因为..那么,为什么它在几年前曾在该项目中起作用,却使人们忙碌而沉默寡言?我不知道。但是“细节” 把男人和男孩分开,或者..最初的广告口号如何?;-)

经过多年(十年的积极开发经验),每当客户提到“活动记录模式”时,我的警钟就会响起。我学会了尝试使他们回到基本的概念阶段,让他们三思而后行,尝试让他们表现出他们的概念上的弱点,或者只是在根本不关心的情况下完全避免它们(最后,您知道的客户还没有知道它想要什么,甚至可能以为它知道但不知道,或者试图免费将概念工作外部化到ME,这使我花费了很多宝贵的时间,几天,几周和几个月的时间,生活太短了……)。

所以,最后:这就是为什么我讨厌这种愚蠢的“活动记录模式”,并且我会并且会尽可能避免它。

编辑:我什至称其为无模式。它不能解决任何问题(模式并非旨在创建语法糖)。它产生了许多问题:所有问题的根源(在这里的很多答案中都提到了..)是,它只是将良好的旧的,成熟的,功能强大的SQL隐藏在受模式定义极为严格的接口后面。

这种模式用语法糖代替了灵活性!

考虑一下,AR能为您解决哪个问题?


1
这是一种数据源架构模式。也许您应该阅读Fowler的企业应用程序体系结构模式?在实际使用模式/ ORM并发现它可以简化多少事情之前,我有与您相似的想法。
MattMcKnight

1
我分享你的感受。当框架不支持复合键时,我闻到了问题。...在SQLAlchemy之前我避免了任何类型的ORM,并且我们经常在较低级别上将其用作SQL生成器。它实现了Data Mapper,并且非常灵活。
Marco Mariani 2010年

1
由于我参与了使用“最新技术” ORM的项目两天,所以实现可能已经成熟(与几年前的工作相比)。也许,我的想法会改变,我们将在三个月后看到:-)
Frunsi 2010年

2
项目已经完成,您知道吗?ORM仍然很糟糕,我在映射问题上浪费了很多时间,这些问题很容易以关系方式表达给一堆“面向对象的代码”。好吧,当然,ORM提供了一种以OOP + SQL-Mix形式表达查询的方法-当然是一种类似OOP的语法-但这比编写SQL查询花费了更多的时间。泄漏的抽象是OOP之上的“ OOPSQLExperiment”-允许用户以OOP语法编写SQL是最糟糕的想法。不,再也不会。
弗伦西(Frunsi)2010年

1
我写原始SQL已有很多年了。Rails AR有时使我感到沮丧,对于被动查询,我几乎同意你的看法,但这是它可以解决的问题:1)使其难以保存未通过验证的数据。2)跟踪自上次持久以来内存中的更改。3)使用点2编写明智的before_save回调以保持记录中的一致性4)after_commit挂钩外部服务触发器。5)良好的DSL,用于将DDL变更组织为变更集(迁移)。(那里仍然很痛苦,但是当> 1个开发人员时,没有模式会更糟。)
Adamantish

6

一些消息使我感到困惑。一些答案将是“ ORM”和“ SQL”之类的。

事实是,AR只是一种简化的编程模式,您可以利用您的域对象在其中编写数据库访问代码。

这些对象通常具有业务属性(bean的属性)和某些行为(通常在这些属性上起作用的方法)。

AR只是对数据库相关任务说“向这些域对象添加一些方法”。

根据我的见解和经验,我不得不说我不喜欢这种模式。

乍一看听起来不错。一些现代Java工具(例如Spring Roo)使用此模式。

对我来说,真正的问题只是与OOP有关。AR模式以某种方式迫使您从对象向基础结构对象添加依赖项。这些基础结构对象使域对象可以通过AR建议的方法来查询数据库。

我一直说,两层是项目成功的关键。服务层(业务逻辑所在的位置或可以通过某种远程处理技术(例如,Web服务)导出的位置)和域层。我认为,如果我们向域层对象添加一些依赖性(不是真正需要的)以解决AR模式,则我们的域对象将很难与其他层或(罕见)外部应用程序共享。

Spring Roo的AR实现很有趣,因为它不依赖于对象本身,而是依赖于某些AspectJ文件。但是,如果以后您不想使用Roo并不得不重构项目,则AR方法将直接在您的域对象中实现。

换个角度来看。想象一下,我们没有使用关系数据库来存储对象。假设应用程序将我们的域对象存储在NoSQL数据库中或仅存储在XML文件中。我们将在我们的域对象中实现执行这些任务的方法吗?我不这么认为(例如,在XM的情况下,我们会将XML相关的依赖项添加到我们的域对象中……我想这真的很可悲)。正如Ar模式所说明的,为什么要在域对象中实现关系数据库方法呢?

综上所述,AR模式听起来更简单,并且适合小型和简单的应用程序。但是,当我们拥有复杂的大型应用程序时,我认为经典的分层体系结构是一种更好的方法。


欢迎来到SO。感谢您的评论,但NullUserException在2011年12月17日的1:17上关闭了该问题,因为它没有建设性
Tony Rad

3

问题是关于活动记录设计模式。不是orm工具。

最初的问题用rails标记,并且是指使用Ruby on Rails构建的Twitter。Rails中的ActiveRecord框架是Fowler Active Record设计模式的实现。


2

关于Active Record的投诉,我主要看到的是,当您围绕一个表创建一个模型,并选择了该模型的多个实例时,基本上就是在执行“ select * from ...”。这对于编辑记录或显示记录是很好的,但是,例如,如果要显示数据库中所有联系人的城市列表,则可以“从...中选择城市”,并且仅获取城市。使用Active Record进行此操作需要您选择所有列,但只能使用City。

当然,不同的实现将对此进行不同的处理。然而,这是一个问题。

现在,您可以通过为要尝试执行的特定操作创建一个新模型来解决此问题,但是有些人会认为这是更多的努力而不是收益。

我,我在挖Active Record。:-)

高温超导


2
“使用Active Record进行此操作将要求您选择所有列,但仅使用City。” 指定select子句实际上非常容易。
MattMcKnight

1

我喜欢SubSonic只做一栏的方式。
要么

DataBaseTable.GetList(DataBaseTable.Columns.ColumnYouWant)

, 要么:

Query q = DataBaseTable.CreateQuery()
               .WHERE(DataBaseTable.Columns.ColumnToFilterOn,value);
q.SelectList = DataBaseTable.Columns.ColumnYouWant;
q.Load();

但是,对于延迟加载,Linq仍然是国王。


1

@BlaM:有时我只是为了加入而实现了一个活动记录。并非总是必须是表<-> Active Record的关系。为什么不使用“ Join语句的结果” <-> Active Record?


1

我将谈论Active Record作为一种设计模式,但我还没有看到ROR。

一些开发人员讨厌Active Record,因为他们会阅读有关编写干净整洁的代码的精明书籍,而这些书籍指出Active Record违反了单一可重复性原则,违反了DDD规则,即域对象应该是持久性无知的,以及此类书籍中的许多其他规则。

Active Record中的第二件事域对象与数据库通常是一对一的关系,在某些系统(多数为n层)中,这可能被认为是一种限制。

那只是抽象的东西,我还没有看到ruby在rails上实际实现这种模式。


0

我看到的Active Records问题是,它总是只有一张表。没关系,只要您只使用一个表即可,但是在大多数情况下,使用数据时,您将在某处进行某种联接。

是的,加入通常不如没有在所有加盟当涉及到性能,但是联接 通常优于“假”参加由第一读取整个表A,然后使用获得的信息阅读和筛选器表B.


@BlaM:你绝对正确。尽管我从未使用过Active Record,但我使用了其他螺栓连接的ORM系统(尤其是NHibernate),但我有两个主要的抱怨:创建对象的愚蠢方法(即.hbm.xml文件,每个文件都可以编译成自己的程序集),而性能下降只是加载对象(NHibernate可以使单核proc尖峰执行几秒钟,而当一个等效的SQL查询几乎不执行任何处理时,它根本不加载任何内容)。当然,并非特定于Active Record,但我发现大多数ORM系统(以及类似ORM的系统)似乎都在
TheSmurf

有许多使用hbm.xml文件的替代方法。参见例如NHibernate.Mapping.Attributes和fluent-nhibernate。
Mauricio Scheffer

关于对象创建性能,我从来没有遇到过这样的性能问题,您可能想与探查器一起检查。
Mauricio Scheffer

@mausch:不需要探查器。这是一个众所周知的问题。不知道它是否适用于最新版本(我尚未在工作中使用该版本)。 ayende.com/Blog/archive/2007/10/26/…– TheSmurf
2009年

4
在搜索结果中使用:joins或:includes IE Customer.find(:all,:include =>:contacts,:conditions =>“ active = 1”)将执行SQL连接,而不是对其中任何一个进行全表扫描。
Tilendor

0

ActiveRecord的问题在于它为您自动生成的查询可能会导致性能问题。

您最终会采取一些不直观的技巧来优化查询,这使您想知道首先手动编写查询是否会更省时。


0

尽管所有其他有关SQL优化的注释当然都是有效的,但我对活动记录模式的主要抱怨是,它通常会导致阻抗不匹配。我喜欢保持域整洁并正确封装,而活动记录模式通常会破坏所有的执行希望。


ActiveRecord 通过让您针对关系模式以OO方式进行编码,实际上解决了阻抗不匹配的问题。
Mauricio Scheffer

关心详细吗?普遍的共识是,按照定义,在关系数据库之后建模的对象不是面向对象的(因为关系数据库并不围绕OO概念(例如继承和多态性)发展)。

有三种已知的方法可以将继承映射到关系模式。参考文献:castleproject.org/ActiveRecord/documentation/trunk/usersguide/...
毛里西奥·雅伯

我认为您将Castle Active Record OSS项目误认为Active Record的设计模式。最初的问题(和我的回答)是指设计模式。Castle Active Record项目包含了一些东西,可以帮助进行OO开发,但是模式本身没有。

我只是引用城堡作为参考。回报率的ActiveRecord的实现单表继承只(martinfowler.com/eaaCatalog/singleTableInheritance.html),但其他战略正在考虑(blog.zerosum.org/2007/2/16/...
毛里西奥·雅伯

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.