随着我对 OOP 的了解越来越多,并开始实施各种设计模式,我不断回想起人们讨厌Active Record的案例。
通常,人们说它不能很好地扩展(以 Twitter 为例)——但没有人真正解释为什么它不能很好地扩展;和/或如何在没有缺点的情况下实现 AR 的优点(通过类似但不同的模式?)
希望这不会变成一场关于设计模式的圣战——我想知道的只是****具体**** Active Record 出了什么问题。
如果它不能很好地扩展,为什么不呢?
它还有什么其他问题?
随着我对 OOP 的了解越来越多,并开始实施各种设计模式,我不断回想起人们讨厌Active Record的案例。
通常,人们说它不能很好地扩展(以 Twitter 为例)——但没有人真正解释为什么它不能很好地扩展;和/或如何在没有缺点的情况下实现 AR 的优点(通过类似但不同的模式?)
希望这不会变成一场关于设计模式的圣战——我想知道的只是****具体**** Active Record 出了什么问题。
如果它不能很好地扩展,为什么不呢?
它还有什么其他问题?
有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 列,映射对象中的所有其他“属性”都将为零,除非您手动重新加载该对象,等等。
我一直发现 ActiveRecord 适用于模型相对扁平的基于 CRUD 的快速应用程序(例如,没有很多类层次结构)。但是,对于具有复杂 OO 层次结构的应用程序,DataMapper可能是更好的解决方案。虽然 ActiveRecord 假设表和数据对象之间的比率为 1:1,但这种关系在更复杂的域中变得笨拙。在他关于模式的书中,Martin Fowler 指出 ActiveRecord 在您的模型相当复杂的情况下往往会崩溃,并建议使用DataMapper作为替代方案。
我发现这在实践中是正确的。在您的域中有很多继承的情况下,将继承映射到 RDBMS 比映射关联或组合更难。
我这样做的方式是让控制器通过这些 DataMapper(或“服务层”)类访问“域”对象。这些并不直接反映数据库,而是充当某些现实世界对象的 OO 表示。假设您的域中有一个 User 类,并且需要在检索该 User 对象时已经加载了对其他对象的引用或集合。数据可能来自许多不同的表,而 ActiveRecord 模式可能会使其变得非常困难。
例如,您的控制器代码通过调用 UserMapper.getUser() 方法的 API 来检索用户对象,而不是直接加载用户对象并使用 ActiveRecord 样式 API 访问数据。正是该映射器负责从它们各自的表中加载任何关联的对象,并将完成的用户“域”对象返回给调用者。
本质上,您只是添加了另一层抽象来使代码更易于管理。无论您的 DataMapper 类是否包含原始的自定义 SQL,还是对数据抽象层 API 的调用,甚至是自己访问 ActiveRecord 模式,对于接收良好的填充用户对象的控制器代码来说都无关紧要。
无论如何,我就是这样做的。
我认为人们为什么“讨厌” ActiveRecord 和它的“错误”之间可能有一组非常不同的原因。
在讨厌的问题上,对任何与 Rails 相关的东西都有很多毒液。至于它有什么问题,它很可能就像所有技术一样,在某些情况下它是一个不错的选择,也存在有更好选择的情况。在我的经验中,如果你不能利用 Rails ActiveRecord 的大部分特性,那就是数据库的结构很糟糕。如果您在没有主键的情况下访问数据,并且违反了第一范式,其中需要大量存储过程来访问数据,那么您最好使用更多只是 SQL 包装器的东西。如果您的数据库结构相对较好,ActiveRecord 可以让您利用它。
添加到回复评论者的主题,这些评论者说 ActiveRecord 中的事情很困难,并带有代码片段 rejoinder
@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)应用程序逻辑 - 实现交互对象的逻辑以及应用程序逻辑对象的创建,删除和列出(!)(不,这些任务很少应该锚定在应用程序逻辑对象本身:你桌子上的纸是否告诉你办公室里所有其他工作表的名称和位置?忘记列出对象的“静态”方法,这很愚蠢,这是一个糟糕的折衷方案,旨在使人类的思维方式适合 [some-not-all-AR-framework-like -]AR 思维)
E) 用户界面——嗯,我将在下面几行中写的内容非常、非常、非常主观,但根据我的经验,基于 AR 构建的项目经常忽略应用程序的 UI 部分——时间浪费在创建晦涩的抽象上. 最后,这样的应用程序浪费了编码人员的大量时间,感觉就像是编码人员为编码人员提供的应用程序,内部和外部都倾向于技术。编码员感觉很好(努力工作终于完成,一切都完成并正确,根据纸上的概念......),客户“只需要了解它需要那样”,因为那是“专业”......好的,对不起,我离题了;-)
好吧,诚然,这一切都是主观的,但这是我的经验(Ruby on Rails 除外,它可能会有所不同,而且我对这种方法的实践经验为零)。
在付费项目中,我经常听到需要从创建一些“活动记录”对象作为更高级别应用程序逻辑的构建块开始。以我的经验,这通常很明显是客户(大多数情况下是软件开发公司)没有一个好的概念,一个大的观点,一个产品最终应该是什么的概述的某种借口。那些客户在僵化的框架下思考(“十年前的项目运行良好......”),他们可能会充实实体,他们可能会定义实体关系,他们可能会分解数据关系并定义基本的应用程序逻辑,但随后他们就停止了把它交给你,认为这就是你所需要的……他们往往缺乏一个完整的应用程序逻辑、用户界面、可用性等概念……他们缺乏大视野,他们缺乏对应用程序的热爱。细节,他们希望你遵循 AR 的方式,因为.. 好吧,为什么,它在几年前的那个项目中起作用,它让人们忙碌而沉默?我不知道。但“细节” 把男人和男孩分开,或者..原来的广告口号是怎样的?;-)
经过多年(十年积极开发经验),每当客户提到“活动记录模式”时,我的警钟就会响起。我学会了试着让他们回到那个重要的概念阶段,让他们三思而后行,试着让他们展示他们的概念弱点,或者如果他们没有辨别力就完全避免他们(最后,你知道,一个客户还没有知道它想要什么,甚至可能认为它知道但不知道,或者试图免费将概念工作外化给我,这花费了我许多宝贵的时间、几天、几周和几个月的时间,生命太短了......)。
所以,最后:这就是为什么我讨厌那种愚蠢的“主动记录模式”,我会并且会尽可能避免它。
编辑:我什至称之为无模式。它不能解决任何问题(模式并不意味着创建语法糖)。它产生了许多问题:所有问题的根源(在此处的许多答案中都提到了......)是,它只是将旧的、开发良好且功能强大的 SQL 隐藏在一个受模式定义极其有限的接口后面。
这种模式用语法糖代替了灵活性!
想一想,AR 为你解决了哪些问题?
有些消息让我感到困惑。一些答案将指向“ORM”与“SQL”或类似的东西。
事实上,AR 只是一种简化的编程模式,您可以在其中利用域对象来编写数据库访问代码。
这些对象通常具有业务属性(bean 的属性)和一些行为(通常对这些属性起作用的方法)。
AR 只是对与数据库相关的任务说“向这些域对象添加一些方法”。
而且我不得不说,从我的观点和经验来看,我不喜欢这种模式。
乍一看,它听起来不错。一些现代 Java 工具(如 Spring Roo)使用这种模式。
对我来说,真正的问题在于 OOP 问题。AR 模式迫使您以某种方式将对象的依赖项添加到基础设施对象。这些基础设施对象让领域对象通过 AR 建议的方法查询数据库。
我一直说,两个层次是项目成功的关键。服务层(业务逻辑驻留或可以通过某种远程技术导出,例如 Web 服务)和领域层。在我看来,如果我们为解析 AR 模式的领域层对象添加一些依赖项(不是真正需要的),我们的领域对象将更难与其他层或(罕见的)外部应用程序共享。
AR 的 Spring Roo 实现很有趣,因为它不依赖于对象本身,而是依赖于一些 AspectJ 文件。但是,如果以后您不想使用 Roo 并且必须重构项目,AR 方法将直接在您的域对象中实现。
另一个角度来看。想象一下,我们不使用关系数据库来存储我们的对象。例如,假设应用程序将我们的域对象存储在 NoSQL 数据库中或仅存储在 XML 文件中。我们会在我们的领域对象中实现执行这些任务的方法吗?我不这么认为(例如,在 XM 的情况下,我们会将与 XML 相关的依赖项添加到我们的域对象中......我认为真的很难过)。那么为什么我们必须在领域对象中实现关系数据库方法,就像 Ar 模式所说的那样?
总而言之,AR 模式听起来更简单,适合小型和简单的应用程序。但是,当我们有复杂的大型应用程序时,我认为经典的分层架构是一种更好的方法。
问题是关于 Active Record 设计模式。不是 orm 工具。
最初的问题是用 rails 标记的,是指使用 Ruby on Rails 构建的 Twitter。Rails 中的 ActiveRecord 框架是 Fowler 的 Active Record 设计模式的实现。
关于 Active Record 的投诉,我看到的主要内容是,当您围绕表格创建模型并选择模型的多个实例时,您基本上是在执行“select * from ...”。这对于编辑记录或显示记录很好,但是如果您想显示数据库中所有联系人的城市列表,您可以执行“从...中选择城市”并且只获取城市. 使用 Active Record 执行此操作需要您选择所有列,但仅使用 City。
当然,不同的实现会以不同的方式处理这个问题。尽管如此,这是一个问题。
现在,您可以通过为您正在尝试做的特定事情创建一个新模型来解决这个问题,但有些人会争辩说这比收益更多的努力。
我,我挖掘 Active Record。:-)
高温高压
尽管关于 SQL 优化的所有其他评论肯定是有效的,但我对活动记录模式的主要抱怨是它通常会导致阻抗不匹配。我喜欢保持我的域干净并正确封装,活动记录模式通常会破坏所有希望。
我喜欢 SubSonic 只做一栏的方式。
任何一个
DataBaseTable.GetList(DataBaseTable.Columns.ColumnYouWant)
, 或者:
Query q = DataBaseTable.CreateQuery()
.WHERE(DataBaseTable.Columns.ColumnToFilterOn,value);
q.SelectList = DataBaseTable.Columns.ColumnYouWant;
q.Load();
但是在延迟加载方面,Linq 仍然是王者。
@BlaM:有时我刚刚为连接结果实现了一个活动记录。不一定总是关系表 <--> Active Record。为什么不是“加入语句的结果”<--> Active Record?
我将谈论 Active Record 作为一种设计模式,我没有见过 ROR。
一些开发人员讨厌 Active Record,因为他们阅读了有关编写干净整洁代码的智能书籍,这些书籍指出 Active Record 违反单一责任原则,违反 DDD 规则,即域对象应该是持久无知的,以及此类书籍中的许多其他规则.
Active Record 中的第二件事域对象往往与数据库是一对一的,这可能被认为是某些系统(主要是 n 层)的限制。
那只是抽象的东西,我还没有看到 ruby on rails 实际实现这种模式。
我在 Active Records 中看到的问题是,它总是只有一张表。没关系,只要您真的只使用那一张表,但在大多数情况下,当您使用数据时,您会在某处进行某种联接。
是的,就性能而言,连接通常比不连接更差,但连接 通常比“假”连接更好,方法是首先读取整个表 A,然后使用获得的信息来读取和过滤表 B。
ActiveRecord 的问题在于它自动为您生成的查询可能会导致性能问题。
您最终会使用一些不直观的技巧来优化查询,这让您想知道首先手动编写查询是否会更省时。
尝试做一个多对多的多态关系。没那么容易。特别是当您不使用 STI 时。