为什么所有的Active Record都讨厌?

时间:2008-08-11 15:30:30

标签: ruby-on-rails design-patterns oop activerecord

随着我越来越多地了解OOP,并开始实施各种设计模式,我不断回到人们讨厌Active Record的案例。

通常,人们说它不能很好地扩展(引用Twitter作为他们的主要例子) - 但没有人真正解释 为什么 它不能很好地扩展;和/或如何在没有缺点的情况下实现AR的优点(通过类似但不同的模式?)

希望这不会变成关于设计模式的神圣战争 - 我想知道的只是****具体**** Active Record有什么问题。

如果不能很好地扩展,为什么不呢?

它还有什么其他问题?

14 个答案:

答案 0 :(得分:90)

ActiveRecord the Design PatternActiveRecord the Rails ORM Library,而且还有很多针对.NET和其他语言的淘汰赛。

这些都是不同的东西。他们大多遵循这种设计模式,但是以许多不同的方式扩展和修改它,所以在任何人说“ActiveRecord糟透了”之前,它需要通过说“哪个ActiveRecord,有堆积?”来证明其合适。

我只熟悉Rails的ActiveRecord,我会尝试解决在使用它时提出的所有抱怨。

  

@BlaM

     

我在Active Records中看到的问题是,它总是只有一个表

代码:

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

这会生成带有LEFT JOIN companies on companies.id = person.company_id的SQL,并自动生成关联的公司对象,因此您可以执行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,除非您手动重新加载该对象,依此类推。

答案 1 :(得分:51)

我一直发现ActiveRecord适用于基于CRUD的快速应用程序,其中模型相对平坦(如同很多类层次结构)。但是,对于具有复杂OO层次结构的应用程序,DataMapper可能是更好的解决方案。虽然ActiveRecord假设您的表与数据对象之间的比率为1:1,但这种关系在更复杂的域中变得难以处理。在他的book on patterns中,Martin Fowler指出ActiveRecord在模型相当复杂的情况下会发生故障,并建议DataMapper作为替代。

我发现这在实践中是真实的。如果你的域中有很多继承,那么将继承映射到RDBMS比映射关联或组合更难。

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

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

基本上,您只是添加另一层抽象来使代码更易于管理。无论您的DataMapper类是包含原始自定义SQL,还是调用数据抽象层API,甚至自己访问ActiveRecord模式,对于接收一个漂亮的,已填充的User对象的控制器代码来说并不重要。

无论如何,我就是这样做的。

答案 2 :(得分:11)

我认为人们对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允许您覆盖默认的延迟加载行为。

答案 3 :(得分:7)

答案 4 :(得分:5)

有些消息让我感到困惑。 有些答案是“ORM”与“SQL”或类似的东西。

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

这些对象通常具有业务属性(bean的属性)和一些行为(通常用于这些属性的方法)。

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

我必须从我的观点和经验中说,我不喜欢这种模式。

乍一看,这听起来不错。一些像Spring Roo这样的现代Java工具使用了这种模式。

对我来说,真正的问题只是关注OOP。 AR模式会强制您以某种方式将对象的依赖项添加到基础结构对象中。这些infraestructure对象允许域对象通过AR建议的方法查询数据库。

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

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

另一种观点。想象一下,我们不使用关系数据库来存储我们的对象。想象一下,例如,应用程序将我们的域对象存储在NoSQL数据库中或仅存储在XML文件中。我们是否会在域对象中实现执行这些任务的方法?我不这么认为(例如,在XM的情况下,我们会将XML相关的依赖项添加到我们的域对象中......我认为真的很难过)。那么为什么我们必须在域对象中实现关系数据库方法,如Ar模式所示?

总而言之,AR模式听起来更简单,适用于小型和简单的应用程序。但是,当我们拥有复杂的大型应用程序时,我认为经典的分层架构是一种更好的方法。

答案 5 :(得分:3)

  

问题是关于活跃的   记录设计模式。不是一个orm   工具。

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

答案 6 :(得分:2)

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

当然,不同的实现方式将以不同方式处理。然而,这是一个问题。

现在,你可以通过为你想要做的具体事情创建一个新模型来解决这个问题,但有些人会认为它比收益更多的努力。

我,我挖掘积极记录。 : - )

HTH

答案 7 :(得分:1)

@BlaM: 有时我会为连接结果实现一个活动记录。并不总是必须是关系表< - >积极记录。为什么不“加入声明的结果”< - >积极记录?

答案 8 :(得分:1)

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

一些开发人员讨厌Active Record,因为他们阅读了关于编写干净利落的代码的智能书籍,而这些书籍指出活动记录违反了单一的resposobility原则,违反了DDD规则,域名对象应该是持久无知的,以及许多其他规则来自这些那种书。

Active Record中的第二个域对象往往与数据库一对一,这可能被认为是某种系统的限制(主要是n层)。

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

答案 9 :(得分:1)

我喜欢SubSonic只做一列的方式。

DataBaseTable.GetList(DataBaseTable.Columns.ColumnYouWant)

,或:

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

但是当谈到延迟加载时,Linq仍然是王者。

答案 10 :(得分:0)

我在Active Records中看到的问题是,它始终只是一个表。没关系,只要您真正使用这一个表,但在大多数情况下处理数据时,您将在某处进行某种连接。

是的,加入在性能方面通常比完全没有加入更糟糕,但加入 通常首先阅读整个表格A,然后使用获取的信息读取和过滤表格B,优于“假”加入

答案 11 :(得分:0)

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

你最终会做一些不直观的技巧来优化查询,让你想知道首先手动编写查询是否会更有效。

答案 12 :(得分:0)

尝试做多对多的多态关系。不那么容易。特别是当你不使用STI时。

答案 13 :(得分:0)

尽管关于SQL优化的所有其他评论肯定都是有效的,但我对主动记录模式的主要抱怨是它通常会导致impedance mismatch。我喜欢保持我的域清洁和正确封装,活动记录模式通常会破坏所有的希望。