随着我越来越了解OOP,并开始实现各种设计模式,我不断回到人们讨厌Active Record的情况。
人们常常说它的伸缩性不好(以Twitter为例),但实际上没有人解释为什么伸缩性不好。和/或如何在没有缺点的情况下实现AR的优点(通过类似但不同的模式?)
希望这不会成为关于设计模式的一场圣战-我只想特别地了解一下Active Record的问题。
如果扩展性不好,为什么不呢?
还有什么其他问题?
随着我越来越了解OOP,并开始实现各种设计模式,我不断回到人们讨厌Active Record的情况。
人们常常说它的伸缩性不好(以Twitter为例),但实际上没有人解释为什么伸缩性不好。和/或如何在没有缺点的情况下实现AR的优点(通过类似但不同的模式?)
希望这不会成为关于设计模式的一场圣战-我只想特别地了解一下Active Record的问题。
如果扩展性不好,为什么不呢?
还有什么其他问题?
Answers:
有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,除非您手动重新加载该对象,依此类推。
我一直发现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对象的控制器代码无关紧要。
无论如何,这就是我的方法。
我认为人们为什么在ActiveRecord上“讨厌”与它有什么“错”之间可能有非常不同的原因。
在仇恨问题上,对Rails相关的任何事物都有很多毒液。至于什么地方出了问题,很可能就像所有技术一样,在某些情况下它是一个不错的选择,在某些情况下还有更好的选择。根据我的经验,您无法利用Rails ActiveRecord的大多数功能的情况是数据库的结构不良。如果您访问的数据没有主键,并且违反了第一种标准格式,即需要大量存储过程来访问数据,那么最好使用一些SQL包装程序。如果您的数据库结构相对良好,则ActiveRecord可以让您利用它。
要添加主题以回复带有代码段重新连接的ActiveRecord困难的评论者的主题
@Sam McAfee假设您的域中有一个User类,并且需要具有在检索该User对象时已经加载的对其他对象的引用或集合。数据可能来自许多不同的表,而ActiveRecord模式可能会使它变得非常困难。
user = User.find(id, :include => ["posts", "comments"])
first_post = user.posts.first
first_comment = user.comments.first
通过使用include选项,ActiveRecord使您可以覆盖默认的延迟加载行为。
我的回答很长而且很晚,甚至还不完整,但是很好的解释为什么我讨厌这种模式,观点甚至情绪:
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能为您解决哪个问题?
before_save
回调以保持记录中的一致性4)after_commit
挂钩外部服务触发器。5)良好的DSL,用于将DDL变更组织为变更集(迁移)。(那里仍然很痛苦,但是当> 1个开发人员时,没有模式会更糟。)
一些消息使我感到困惑。一些答案将是“ 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模式听起来更简单,并且适合小型和简单的应用程序。但是,当我们拥有复杂的大型应用程序时,我认为经典的分层体系结构是一种更好的方法。
关于Active Record的投诉,我主要看到的是,当您围绕一个表创建一个模型,并选择了该模型的多个实例时,基本上就是在执行“ select * from ...”。这对于编辑记录或显示记录是很好的,但是,例如,如果要显示数据库中所有联系人的城市列表,则可以“从...中选择城市”,并且仅获取城市。使用Active Record进行此操作需要您选择所有列,但只能使用City。
当然,不同的实现将对此进行不同的处理。然而,这是一个问题。
现在,您可以通过为要尝试执行的特定操作创建一个新模型来解决此问题,但是有些人会认为这是更多的努力而不是收益。
我,我在挖Active Record。:-)
高温超导
我喜欢SubSonic只做一栏的方式。
要么
DataBaseTable.GetList(DataBaseTable.Columns.ColumnYouWant)
, 要么:
Query q = DataBaseTable.CreateQuery()
.WHERE(DataBaseTable.Columns.ColumnToFilterOn,value);
q.SelectList = DataBaseTable.Columns.ColumnYouWant;
q.Load();
但是,对于延迟加载,Linq仍然是国王。
我将谈论Active Record作为一种设计模式,但我还没有看到ROR。
一些开发人员讨厌Active Record,因为他们会阅读有关编写干净整洁的代码的精明书籍,而这些书籍指出Active Record违反了单一可重复性原则,违反了DDD规则,即域对象应该是持久性无知的,以及此类书籍中的许多其他规则。
Active Record中的第二件事域对象与数据库通常是一对一的关系,在某些系统(多数为n层)中,这可能被认为是一种限制。
那只是抽象的东西,我还没有看到ruby在rails上实际实现这种模式。
我看到的Active Records问题是,它总是只有一张表。没关系,只要您只使用一个表即可,但是在大多数情况下,使用数据时,您将在某处进行某种联接。
是的,加入通常不如没有在所有加盟当涉及到性能,但是联接 通常优于“假”参加由第一读取整个表A,然后使用获得的信息阅读和筛选器表B.
尽管所有其他有关SQL优化的注释当然都是有效的,但我对活动记录模式的主要抱怨是,它通常会导致阻抗不匹配。我喜欢保持域整洁并正确封装,而活动记录模式通常会破坏所有的执行希望。
尝试建立多对多态关系。没那么容易。尤其是当您不使用STI时。