随着我越来越多地了解OOP,并开始实施各种设计模式,我不断回到人们讨厌Active Record的案例.
通常,人们说它不能很好地扩展(引用Twitter作为他们的主要例子) - 但没有人真正解释为什么它不能很好地扩展; 和/或如何在没有缺点的情况下实现AR的优点(通过类似但不同的模式?)
希望这不会变成关于设计模式的神圣战争 - 我想知道的只是****特别****Active Record有什么问题.
如果它不能很好地扩展,为什么不呢?
还有什么其他问题?
有ActiveRecord的设计模式和ActiveRecord的Rails ORM库,还有很多针对.NET和其他语言的淘汰赛.
这些都是不同的东西.他们大多遵循这种设计模式,但是以许多不同的方式扩展和修改它,所以在任何人说"ActiveRecord糟透了"之前,它需要通过说"哪个ActiveRecord,那里有堆?"来证明它.
我只熟悉Rails的ActiveRecord,我会尝试解决在使用它的过程中提出的所有抱怨.
@BlaM
我在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的情况,它很容易完成.
@Tim Sullivan
...并且你选择了几个模型实例,你基本上是在做一个"select*from ..."
码:
people = Person.find(:all, :select=>'name, id')
这只会从数据库中选择名称和ID列,映射对象中的所有其他"属性"将只是nil,除非您手动重新加载该对象,依此类推.
我一直发现ActiveRecord适用于基于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 在数据库和应用程序代码之间创建" 强绑定 " 的" 薄层 " .这解决了没有逻辑,没有任何问题,根本没有问题.恕我直言,它没有提供任何值,除了程序员的一些语法糖(然后可能使用"对象语法"来访问存在于关系数据库中的某些数据).创造一些安慰的程序员的努力应该(恕我直言...)更好地投资于低级别的数据库访问工具,例如简单,轻松,朴实一些变化和类似的方法(当然,概念和优雅大大与变化使用的语言).hash_map get_record( string id_value, string table_name, string id_column_name="id" )
2)长版本:在我对事物进行"概念控制"的任何数据库驱动的项目中,我避免了AR,这很好.我通常构建一个分层架构(您迟早会将您的软件分层,至少在中型到大型项目中):
A1)数据库本身,表,关系,甚至是DBMS允许的一些逻辑(MySQL现在也成长了)
A2)通常,不仅仅是一个数据存储:文件系统(数据库中的blob并不总是一个好的决定......),遗留系统(想象一下你自己"如何"访问它们,许多种类可能......但那就是不是重点...)
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方式,因为......好吧,为什么,它在几年前的那个项目中起作用,它让人们忙碌而沉默?我不知道.但是"细节"将男人与男孩分开,或者......原来的广告口号是怎样的?;-)
经过多年(十年的积极开发经验),每当客户提到"活动记录模式"时,我的警铃响起.我学会了试图让他们回到那个基本的概念阶段,让他们三思而后行,试着去展示他们的概念弱点,或者如果他们不敏感就完全避免它们(最后,你知道,还有一个客户还没有知道它想要的,甚至认为它知道,但没有,或者试图外部化的概念工作,我要免费的,花了我许多宝贵的时间,天,周和我几个月的时间,生活太短暂......).
所以,最后:这就是为什么我讨厌那种愚蠢的"主动记录模式",而我尽可能地避免它.
编辑:我甚至称之为无模式.它没有解决任何问题(模式不是为了创造语法糖).它产生了许多问题:它的所有问题的根源(在这里的许多答案中都提到了......),它只是隐藏了良好的老式开发和强大的SQL背后的接口,模式定义非常有限.
这种模式取代了语法糖的灵活性!
想一想,AR为你解决了哪个问题?
有些消息让我感到困惑.一些答案是"ORM"与"SQL"或类似的东西.
事实上,AR只是一种简化编程模式,您可以利用域对象来编写数据库访问代码.
这些对象通常具有业务属性(bean的属性)和一些行为(通常用于这些属性的方法).
AR只是说"向这些域对象添加一些方法"到数据库相关的任务.
从我的观点和经验来看,我不得不说,我不喜欢这种模式.
乍一看它听起来不错.一些像Spring Roo这样的现代Java工具使用了这种模式.
对我来说,真正的问题只是关注OOP.AR模式会强制您以某种方式将对象的依赖项添加到基础结构对象中.这些infraestructure对象允许域对象通过AR建议的方法查询数据库.
我总是说两层是项目成功的关键.服务层(业务逻辑所在的位置或可以通过某种远程处理技术导出,例如Web服务)和域层.在我看来,如果我们为域图层对象添加一些依赖关系(不是真的需要)来解析AR模式,我们的域对象将更难与其他层或(罕见)外部应用程序共享.
Spring的Roo实现很有意思,因为它不依赖于对象本身,而是依赖于一些AspectJ文件.但是如果以后你不想使用Roo并且必须重构项目,那么AR方法将直接在你的域对象中实现.
另一个角度来看.想象一下,我们不使用关系数据库来存储我们的对象.想象一下,例如,应用程序将我们的域对象存储在NoSQL数据库中或仅存储在XML文件中.我们是否会在域对象中实现执行这些任务的方法?我不这么认为(例如,在XM的情况下,我们会将XML相关的依赖项添加到我们的域对象中......我认为真的很难过).那么为什么我们必须在域对象中实现关系数据库方法,如Ar模式所示?
总而言之,AR模式听起来更简单,适用于小型和简单的应用.但是,当我们拥有复杂的大型应用程序时,我认为经典的分层架构是一种更好的方法.