ORM(对象关系映射)的灾难

977 阅读14分钟

ORM的产生,本身源于大厂政治。程序语言方要面向对象。而数据库大厂要用SQL。最早的ORM,那是1995年的TOPLink。有由Smalltalk语言编写。后被移植到了JAVA。但后来,JAVA有了知名的Hibernate。Hibernate甚至弄出了一个HQL,或许它的目标想一统天下。

面向对像与关系数据库是两套技术规范。关系数据库与面向对象程序不匹配,这种不匹配被称为对象关系阻抗不匹配。当然,这个阻抗并不是物理上可测的阻抗,而是两套技术体系的规范。

比如,在数据库体系中,有大量无需声明与创建的,比如,表,字段等,均可以直接访问。而在面向对象的程序语言中,凡是对象就需要用new运算符创建对象。数据库体系是的时间的。而面向对象的程序语言中,对象完全是动态的,无时间概念。

正因为有对象关系阻抗不匹配,所以,才需要ORM。但是,ORM至今20多年了,几乎没有一个成功的。第一个原因,是体系约束,开发者总想完全切合面向对象的语言。可是关系数据库中的SQL则是与编程语言天生对不上。第二,大厂也没有出手,虽说JAVA当年弄了Hibernate,但是,这玩艺完全走错了方向。它是想用面象对象的程序语言重建一套关系数据库的体系,而不只是解决不匹配的桥梁。可能,他们当时想着面向对象的数据库的未来。然而,面向对象的数据库,今天一直没有实现。当然,有一个东西可以算得上,那就是ElasticSearch。但它只是面向对象的索引,底层仍是关系数据库。并且,不难发现,现然有很多人都在用SQL来代替它的查询DSL。这不得不说,面向对象对于关系数据库仍是缺少能力。而微软当年数据库仍是用SQL,跟大厂走都没有错,同时,当时由于他是独家,开发环境集成查询构造器,所以,早期根本没能ORM这一说。所以,当年多数从微软转向开源程序的人,很多人都不接受ORM。可后来,微软件弄出了个LINQ。第三,至今也没有一个开源,使其形成一个ORM的标准。这当然是一个超级难的事情,因为,数据库厂商多,编程语言厂商也多。第四,对象关系这么多年来,没 有一家愿意向对方靠拢。没有一个编程语言数据类型中能够带上数据库中的数据类型。而程序语言中,从来也没有出现过无需创建,可用名称访问的对象。男女平权,公说公有理,婆说婆有理。阴阳合历,你过你的年,我过我的年。

现在,关系数据库成功了,SQL的位置已无可撼动。同样,面向对像的程序语言也成功了。至少,现在,普通人只要了解三个对象就能写程序了。这三个基本对象就是:Model模型,View视图,Controller控制器。其它的对象,都可以当成用类封装的函数库,没有其它。所以,有人说,面向对象,不过如此。这是因为,真正了解23个设计模式的人并不是很多的。

但ORM对软件业、互联网产业、计算机技术发展带来的灾难是巨大的。第一大灾则是:增加了程序员的负担,一个老程序员,如果用的编程语言多,那么,他用过几套ORM库的数量,肯定不是个位数。其版本的数量,可能有数十个到上百个。并且,还会经常寻找大量的ORM库,目的是什么?第一,则是ORM的复杂度。第二则是,不断有人推出各种开源的ORM。ORM表面上是为了减少程序员的学习成本,但本质上,没有一个ORM可使程序员无需关注数据库的细节,因为有抽象漏洞法则。这是Joel在2002年提出的,所有不证自明的抽象都是有漏洞的。抽象泄漏是指任何试图减少或隐藏复杂性的抽象,其实并不能完全屏蔽细节,试图被隐藏的复杂细节总是可能会泄漏出来。抽象漏洞法则说明:任何时候一个可以提高效率的抽象工具,虽然节约了我们工作的时间,但是,节约不了我们的学习时间。代码生成工具如ORM等Hibernate都是这种思路,它抽象了一些东西,但是,所有的抽象机制都是有漏洞的。唯一可以处理漏洞的方法就是知道抽象的原理,都抽象了些什么东西。所以,在你了解抽象原理的过程中,时间之神将讨回你之前节约的时间。

ORM带来的第二大灾难则是,浪费了巨大的能源。因为,早期的OM均是技术路线,Hibernate绝对是一个典型,还有几乎完全照抄的PHP中的Doctrine。这里并不是指技术路线不好,技术路线可以放在实验室中,也可以放到研究所中。但早期技术路线的产品,不只是让程序当了小白鼠,同时增加了大量的不必要的代码,造成了很多的性能开销,这无疑是浪费能源。直到后来,Roby On Rails出现,它向人们提供了ActiveRecord的ORM之后,全世界才为之眼前一亮,原来ORM还可以这么做。ActiveRecord几乎疯靡了所有相关的编程语言。JAVA有之,Python有之,PHP也有……。然而,用户体验最好的至今只能算PHP中的Eloquent。它在用户体验(程序员的编程体验)方面几乎可算是全于球第一。并且,它也有了Python的版本:Orator。

ORM的第三大灾难则是让软件增加了大量的开发成本,很多用户为此付出了高昂的代价,而大量的创业公司,如果没有复杂或者垃圾的ORM,或许公司就成功了。但很多公司却消失了。一个重要的原因之一,就是选错了ORM。

ORM的第四大灾难,则技术选型障碍。一般软件开发,首先是要选开发框架,而现如今一个开发框架中如果没有ORM,那就不成为框架。而如果你选了一个不合适的框架,ORM可能会毁了你,并且,你还换不了。最典型的就是ThinkPhp,它的参数是可以 (a, b, c) 也可以 (['a'=>['b','c']]),这种无文档,不透明的约定,你只能花时间花它的源码,但源码如果是七八层IF嵌套,你是什么心情?再如allowFields是模型中的,如果你在和二步调,将引发一个错误。但是,没有多少创业团队,是了解什么样的ORM才是好的ORM,最后,导致项目一拖再拖,一切均在常理之中。

ORM的第五大灾难,则是,扼杀了大量的程序员的创造力,全世界有大量的程序员都在写ORM,都在写框架,简单来说,就是不知天高地厚,创造了无数的垃圾代码。假如这些程序员如果把精力放在其它方面,或许会有更好的创造。但是,事实不是如此。

既然ORM这么烂,为什么还是有大量的人在用呢,第一种,是真正想通过ORM达到某些目的,比如,程序规范,代码可读,开发效率,程序安全。但现在的ORM,程序规范上算是做得可以。而代码可读,DBA根本无法与程序员联调代码,经常要程序员把SQL语句打出来。要论开发效率,很多看似加快开发速度的,结果大量的BUG浪费的时间不在少数,写代码时间短了,但调试则是以数倍的量级增加。再说安全,自从ActiveRecord开始之后,数据访问变得越来越方便,那就是越来越暴露。在这种情况下,ORM的完全漏洞是最易出的地方。第二种,需要ORM的目的,则只是用于心理安慰,认为,裸SQL不安全,SQL注入相对容易,安全不可控。其次,因为听说ORM有程序规范,代码可读,开发效率的优势,所以,从众。本质上,完全是在百分百的心理安慰。

那么,ORM是不是一个永远解不开的死结呢?这要看多个方面。第一是,对象与关系双方是否可以妥协,如果有一方妥协,则会有新的创新。其次,假如真的有一个真正的ORM标准机构,能够推动ORM的标准化,那么,ORM会向良性的方向发展。第三,社会力量中,高校科研团体的力量是相当强的,如果有这些力量加入,或许ORM的标准化的进程会有所加块。

那么,在现实的情况下,怎样才能算是一个好的ORM呢?一个好的ORM应当会有以下的良好的结构。首先,DBAL,即数据库访问层,良好的数据访问层的支持并不难,但有些ORM,不时让数据库断线,或者让数据库死锁。这些都是最低要求了,至于集群,主从模式的支持在现今也是必须的。第二,查询构造器。一个好的ORM,必然要让程序员方便构造查询。而对SQL的近似等价映射则是必须的。为什么呢?试想想,等价映射,对于程序员来说,学习成本最低。比如:ctx.select( field.id, field.name.as(student_name)).from(student).where(condition.age.lte(30);这样的语句,程序员好懂,DBA也明白。当然,目前程序不支持名称访问对象,也没有函数支持。第三,模型层,好的模型层应当是为程序员的最常用需求提供最佳用户体验。Ruby On Rails的ActiveRecord的成功,就是提供了最好的单表的增删改查操作。从产品角度来说,产品要易用,好用,有用。易用性是第一位的。如果没有其它的替代品,易用,好用并不重要。只要有用就够了。第四,数据处理层,这一层用于数据的后处理,一般使用具有扩展功能的集合对象。 查询构造器与模型层是现有ORM的架构。这一架构可以算是最混乱的架构。原因在于,模型的第一功能是为了提供数据库对象映射 ,模型可在映射表与视图。同时,映射表中数据。但这是通过对象与属性映射的,而不是名称访问对象。所以,要用时,都要用new运算符。说白了,模型就是数据映射的配置与查询API对象的合成体。目的是从模型出发,少写代码,就能轻松无感使用查询构造器,最后通过集合获得结果。模型第一解决了,告诉程序员,数据库中有哪些表,表中有哪些字段,但绝大多数模型,都不能告诉程序员,有哪些函数,以及有哪些程序逻辑关键字或表达式。但是,模型的这种做法似乎是对象关系映射中的目前最优解决方案。试想想,如果映射使用全名称对象树。现在IDE都能动态提示数组成员,所以,看起来是一个不错的办法。但目前,似乎也没有这样的解决方案。另外,模型给用户提供的是单表操作。多表则使用要主查询结果再查的方式,将本来JOIN的语句给拆开了,hasOne,hasMany等ActiveRecord的方法虽然好用,但对于性能,却是灾难。另一方面,MVC本质上并不是绝对完善的架构,由于ActiveRecord,导致模型中有一两千行代码也是常事。为此,人们改用了Reposotory+Service来进一步改善MVC。Model只能是一个表,Reposotory + Service则可以打破这个限制。

好的ORM本质上是开发体验,对于一个软件与互联网企业来说,开发速度快是王道,所以,ORM用一些效率牺牲来换取开发速度则是必须的。毕境内存不值钱。但是,一些初级的不成型的ORM一样也会给项目带来灾难。比如,thinkPHP的Model,主键不是必配项,但运行时就有大量的show table查询表结构的SQL,仍对外称高效,高效在哪里呢?而在开发体验上,则是ORM最难实现的。首先是动态提示与自动完成。因为,数据库是名称访问对象,与OOP语言完全不同。IDE只能动态提示现有类中的属性与方法。SQL属于特别的DSL,一切需要基于与数据库通信获取表,字段等信息,这么做,则又形成了数据库的客户端了。所以,绝大多数都是在寻求平衡,用最简单的方法提供最方便快速开发的功能。但是,对于程序员来说,总是不爽,因为,总与SQL客户端有差别。但到目前为止,没有一个可以完全实现SQL程序中百分百构建的ORM。早期ORM都有这样的追求,但因为,OOP程序本身的限制,因而大大低了开发体验。自从ActiveRecord出现之后,人们发现,简单才是王道。这也是PHP, Golang能被大量用户接受的原因。同样也是MVC大行其道的原因。道理简单,MVC,你只要了解这三个类基本就会了。从这一点来说,除非OOP有名称访问对象支持,否则,对象关系阻抗不匹配将永远存在下去。ORM的灾难将会继续。在此奉劝一下那些不自量力的框架或开源的开发者,没有对两套体系的深厚的了解,没有对前人积累深厚的了解,不要去开发什么ORM。奉劝一下创业团队的技术负责人,技术选型时一定要慎重,至少,在github上选择STAR很多的开源,包括符合开源规范的开源,比如,文档是否够详细,是否经过单元测试,有没有经过TRAVIS-CI等各类CI程序做过持续集成。测试代码覆盖率是多少,这些都值得参考。不要像某些框架,一直是29%。我真闹不懂,这样的覆盖率,居然还有大量的用户,看宣传,听忽悠。而且还说什么,免费的,应当知道感恩。免费的,你就得主动当小白鼠吗?

最后,给大家推荐一些好的ORM。对于.NET,肯定是LINQ,这个官方的相当牛的产品,不用我多说。JAVA,则是JOOQ,凡是用过的,心里都明白。JOOQ作者的理念,SQL就是SQL,不是程序,所以,他打破了以往ORM的常规,完全属于一种创新,并且,这种创新给程序员带来的开发体验是相当好的。JOOQ出来之后,国内有人称,ORM彻底失败了。是有,ORM提供不了像JOOQ那样的体验。但我们可以说,JOOQ才是真正的ORM。Python,Orator,这是从移植php Eloguent的项目。对于PHP,绝对是Eloquent,当然,Eloquent解决了开发中最常见的痛点,但是,复杂SQL就没有那么好的体验了。要好的体验,Dsql算是一个较好的选择。老牌的Propel虽好,但是,现在开发主流都是Laravel或Swoft,Swoft中用的也是Eloguent。当然,如果你硬要说TP,那你是天生受虐狂,我就不多说了。