为什么所有的活动logging都讨厌?
随着我越来越多地了解面向对象,并开始实施各种devise模式,我不断回到人们讨厌活动logging的情况 。
人们经常说它不能很好地扩展(引用Twitter作为主要例子),但是没有人真正解释为什么它不能很好地扩展; 和/或如何在没有缺点的情况下实现AR的优点(通过类似但不同的模式?)
希望这不会变成一场关于devise模式的圣战 – 我想知道的仅仅是特别的**** Active Record有什么问题。
如果不好,为什么不呢?
还有什么其他问题?
有ActiveRecord的devise模式和ActiveRecord的Rails的ORM库 ,还有很多的.NET和其他语言的敲门砖。
这些都是不同的东西。 他们大多遵循这种devise模式,但是以多种方式扩展和修改它,所以在任何人说“ActiveRecord Sucks”之前,需要通过说“哪个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
生成与LEFT JOIN companies on companies.id = person.company_id
生成的SQL,并自动生成关联的公司对象,因此您可以执行people.first.company
,因为数据已经存在,所以无需点击数据库。
@ pix0r
Active Record的固有问题是数据库查询是自动生成和执行的,以填充对象并修改数据库logging
码:
person = Person.find_by_sql("giant complicated sql query")
这是不好的,因为它是丑陋的,但对于你只是简单的,只需要写入原始SQL的情况下,它很容易完成。
@Tim Sullivan
…然后你select模型的几个实例,你基本上是做一个“select * from …”
码:
people = Person.find(:all, :select=>'name, id')
这将只从数据库中select名称和ID列,映射对象中的所有其他'属性'将只是零,除非您手动重新加载该对象,等等。
我一直发现,ActiveRecord适用于模型相对平坦的(如在,不是很多类层次的)快速的基于CRUD的应用程序。 但是,对于具有复杂OO层次结构的应用程序, DataMapper可能是更好的解决scheme。 虽然ActiveRecord假设表与数据对象之间的比例为1:1,但是这种关系在更复杂的领域中变得笨拙。 在他关于模式的书中 ,Martin Fowler指出,在你的模型相当复杂的情况下,ActiveRecord往往会崩溃,并build议使用DataMapper作为替代。
我在实践中发现这是真实的。 在您的域中有很多inheritance的情况下,将inheritance映射到RDBMS比映射关联或组合更困难。
我这样做的方式是通过您的控制器通过这些DataMapper(或“服务层”)类来访问“域”对象。 这些不直接镜像数据库,而是作为您的面向一些真实世界对象的OO表示。 假设您的域中有一个User类,并且需要引用或检索到该User对象时已经加载的其他对象的集合。 数据可能来自许多不同的表格,而ActiveRecord模式可能会使其变得非常困难。
例如,您不需要直接加载User对象并使用ActiveRecord风格的API访问数据,而是通过调用UserMapper.getUser()方法的API来获取User对象。 正是这个mapper负责从它们各自的表中加载任何关联的对象,并将完成的User“domain”对象返回给调用者。
本质上,你只是增加了一个抽象层,使代码更易于pipe理。 无论您的DataMapper类是包含原始自定义SQL,还是调用数据抽象层API,或者甚至是自己访问ActiveRecord模式,对于接收良好的,填充的User对象的控制器代码而言都无关紧要。
无论如何,这就是我如何做到的。
我认为,为什么人们对ActiveRecord感到“厌恶”,以及它的“错误”之间可能存在着截然不同的一系列原因。
在讨厌的问题上,Rails相关的东西有很多毒气。 至于什么是错误的,它可能就像所有的技术一样,在某些情况下它是一个很好的select和情况下有更好的select。 根据我的经验,您不能充分利用Rails ActiveRecord的大部分function的情况是数据库结构严重不足的地方。 如果您正在访问没有主键的数据,那么违反第一个正常forms的事情,在访问数据所需的存储过程很多的情况下,最好使用更多的仅仅是SQL包装的东西。 如果你的数据库结构比较好,ActiveRecord可以让你利用这个优势。
添加回复评论者的主题,他们用ActiveRecord中的代码片段回复
@Sam McAfee假设您的域中有一个用户类,并且需要引用或检索到该用户对象时已经加载的其他对象的集合。 数据可能来自许多不同的表格,而ActiveRecord模式可能会使其变得非常困难。
user = User.find(id, :include => ["posts", "comments"]) first_post = user.posts.first first_comment = user.comments.first
通过使用include选项,ActiveRecord可以覆盖默认的延迟加载行为。
我长久迟到的答案,甚至不完整,但一个很好的解释为什么我讨厌这种模式,意见,甚至一些情绪:
1)短版本:Active Record在数据库和应用程序代码之间创build一个“ 强绑定 ”的“ 薄层 ”。 它不能解决任何问题,也没有任何问题。 恕我直言,它不提供任何价值,除了程序员的一些语法糖 (然后可能使用“对象语法”来访问一些关系数据库中存在的数据)。 (IMHO …)可以更好地投入到低级别的数据库访问工具中,例如简单,简单,简单的hash_map get_record( string id_value, string table_name, string id_column_name="id" )
一些变种hash_map get_record( string id_value, string table_name, string id_column_name="id" )
和类似的方法(当然,这些概念和雅致因使用的语言而异)。
2)长版本:在任何我对事物的“概念控制”的数据库驱动的项目中,我避免了AR,这是好的。 我通常会build立一个分层的体系结构 (你至less在大中型项目中分层分割你的软件):
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();
更容易使用le=new LogEntry(); le.time=now(); le.text="I am a log message"; le.Insert();
le=new LogEntry(); le.time=now(); le.text="I am a log message"; le.Insert();
。 例如:在你的应用程序的日志视图中使用“日志条目对象”可以工作100,1000甚至10000条日志行,但迟早你将不得不优化 – 我敢打赌,在大多数情况下,你只会在你的应用程序逻辑中使用这个漂亮的SQL SELECT语句(这完全打破了AR的想法..),而不是用僵硬的固定AR思想框架包装这个小的语句,有很多代码包装和隐藏它。 你写作和/或编写AR代码浪费的时间本来可以投入到一个更为巧妙的界面中,以读取日志条目列表(许多方法,天空是极限)。 编程人员应该敢于发明新的抽象来实现符合预期应用的应用逻辑,而不是愚蠢地重新实现愚蠢模式 ,这听起来不错!
D)应用程序逻辑 – 实现交互对象的逻辑,创build,删除和列出(!)应用程序逻辑对象(否),这些任务应该很less固定在应用程序逻辑对象本身:桌面上的纸张你的办公室所有其他表的名称和位置忘记了“静态”方法的列表对象,这是愚蠢的,一个坏的妥协,使人类的思维方式适合[一些非全AR框架 – ] AR思维)
E)用户界面 – 我将在下面写的内容非常非常主观,但以我的经验来看,构build在AR上的项目通常忽略了应用程序的UI部分 – 时间被浪费在创build模糊的抽象。 最终,这样的应用浪费了很多编码人员的时间,而且感觉就像是编码人员的应用程序,技术倾向于内部和外部。 编码员感觉很好(根据纸上的概念,终于完成了所有的工作,完成了所有的工作,并且正确无误),而且客户“只需要知道它需要这样做”,因为那是“专业的”。好吧,对不起,我离题了;-)
不过,这一切都是主观的,但是我的经验(排除了Ruby on Rails,可能会有所不同,而且我没有这种方法的实践经验)。
在付费项目中,我经常听到需要开始创build一些“活动logging”对象作为上层应用程序逻辑的构build块。 根据我的经验,这显然经常是某种理由,因为客户(大多数情况下是一个软件开发公司)没有一个好的概念,一个大的观点,对产品最终的概述。 那些客户在僵化的框架下思考(“十年前的项目中运行良好”),他们可能会充实实体,可能会定义实体关系,可能会分解数据关系并定义基本的应用逻辑,然后停止并把它交给你,并认为这就是你所需要的……他们往往缺乏一个完整的应用程序逻辑,用户界面,可用性等概念……他们缺乏大的观点,他们缺乏对细节,他们希望你遵循AR方式的事情,因为……那么为什么它在几年前就在那个项目中工作过,它让人们保持忙碌和沉默? 我不知道。 但是“细节”把男人和男人分开,或者说,原来的广告口号是怎么样的呢? 😉
经过多年(十年积极的开发经验),每当客户提到“积极的logging模式”,我的闹钟响了。 我学会了试着让他们回到那个重要的概念阶段 ,让他们三思而后行,试着展示他们的概念弱点,或者如果他们不明智,就避免他们(最终,你知道,一个还没有的客户知道自己想要什么,也许甚至认为它知道但不知道,或者试图将概念工作外在化给我免费的ME,花费我许多宝贵的时间,日子,周和月,我的时间,生活太短…)。
所以,最后:这一切都是为什么我讨厌这个愚蠢的“积极的logging模式”,我会尽可能地避免它。
编辑 :我甚至会称这是一个无模式。 它不能解决任何问题(模式并不意味着创build语法糖)。 它创造了许多问题:它的所有问题的根源(在这里的许多答案中提到)是, 它只是隐藏了由模式定义非常有限的界面背后的老的发达和强大的SQL。
这种模式取代了语法糖的灵活性!
想一想,AR解决了哪个问题?
有些消息让我感到困惑。 一些答案将“ORM”与“SQL”或类似的东西。
事实是,AR只是一个简化编程模式,您可以利用您的域对象来编写数据库访问代码。
这些对象通常具有业务属性(bean的属性)和一些行为(通常在这些属性上工作的方法)。
AR只是说“向这些域对象添加一些方法”到数据库相关的任务。
我不得不说,从我的观点和经验来看,我不喜欢这种模式。
一见钟情听起来不错。 一些像Spring Roo这样的现代Java工具使用这种模式。
对我来说,真正的问题就是面对面的问题。 AR模式强制您以某种方式将对象中的依赖项添加到基础结构对象中。 这些基础结构对象让域对象通过ARbuild议的方法来查询数据库。
我一直说两层是项目成功的关键。 服务层(商务逻辑所在的位置,或者可以通过某种远程技术(例如Web服务)导出)和领域层。 在我看来,如果我们添加一些依赖关系(不是真的需要)到领域层对象来解决AR模式,我们的领域对象将很难与其他层或(罕见的)外部应用程序共享。
AR的Spring Roo实现很有趣,因为它不依赖于对象本身,而是在一些AspectJ文件中。 但是如果以后你不想和Roo一起工作,并且需要重构项目,AR方法将直接在你的域对象中实现。
另一个angular度来看。 想象一下,我们不使用关系数据库来存储我们的对象。 想象一下,应用程序将我们的域对象存储在NoSQL数据库中,或者仅存储在XML文件中。 我们是否会实现在我们的域对象中执行这些任务的方法? 我不这么认为(例如,在XM的情况下,我们将XML相关的依赖关系添加到我们的域对象…真的很伤心,我想)。 那么为什么我们必须在域对象中实现关系数据库方法,就像Ar模式所说的那样?
总而言之,AR模式听起来更简单,更适合小型和简单的应用。 但是,当我们有复杂的大型应用程序,我认为经典的分层架构是一个更好的方法。
问题是关于Active Recorddevise模式。 不是一个orm工具。
最初的问题是用rails标记的,指的是在Ruby on Rails中构build的Twitter。 Rails中的ActiveRecord框架是Fowler的Active Recorddevise模式的实现。
我所看到的关于Active Record的投诉的主要事情是,当你围绕一个表创build一个模型,并且select模型的几个实例时,你基本上正在做一个“select * from …”。 这对于编辑logging或者显示logging来说是很好的,但是如果你想要显示数据库中所有联系人的城市列表,你可以select“从…中select城市”,只有城市。 这样做与Active Record将要求您select所有列,但只使用城市。
当然,不同的实现将以不同的方式处理。 不过,这是一个问题。
现在,你可以通过为你想要做的具体事情创build一个新的模型来解决这个问题,但是有些人会认为这比获得好处更多的努力。
我,我挖掘活动logging。 🙂
HTH
我喜欢SubSonic只做一列的方式。
或
DataBaseTable.GetList(DataBaseTable.Columns.ColumnYouWant)
, 要么:
Query q = DataBaseTable.CreateQuery() .WHERE(DataBaseTable.Columns.ColumnToFilterOn,value); q.SelectList = DataBaseTable.Columns.ColumnYouWant; q.Load();
但Linq在延迟加载时仍然是国王。
@BlaM:有时候我为一个连接的结果实现了一个活动的logging。 并不总是必须是关系表< – >活动logging。 为什么不“join声明的结果”< – >活动logging?
我将要讨论Active Record作为devise模式,我还没有看到ROR。
一些开发者讨厌Active Record,因为他们阅读有关编写干净整洁的代码的聪明书,并且这些书指出活动logging违反单个resposobility原则,违反DDD规则,域对象应该是持久的愚昧,以及这些书中的许多其他规则。
Active Record中的第二个域对象往往与数据库是一一对应的,这可能被认为是某种系统(主要是n层)的限制。
那只是抽象的东西,我还没有看到Ruby on Rails实际执行这种模式。
我在Active Records看到的问题是,它总是只有一张桌子。 没关系,只要你真的只使用一张表,但是在大多数情况下使用数据的时候,你会在某个地方进行某种连接。
是的, 在性能方面, 连接 通常比没有连接要糟糕,但通过首先读取整个表A,然后使用获得的信息来读取和过滤表B, 连接 通常比“假”连接好。
ActiveRecord的问题是,它自动为您生成的查询可能会导致性能问题。
你最终会做一些不直观的技巧来优化查询,让你想知道如果手工编写查询会更有效率。
虽然所有关于SQL优化的其他评论肯定是有效的,但是我对主动logging模式的主要抱怨是它通常会导致阻抗不匹配 。 我喜欢保持我的领域清洁,并妥善封装,活跃的logging模式通常会破坏所有的希望。
尝试做一个多对多的多态关系。 不那么容易。 特别是当你不使用STI。