大型项目如何选择ORM:Active Record VS Data Mappers

在大型Web项目中ORM有着举足轻重的作用,非常考验架构师的设计水平,我见过的失败项目大部分都是ORM模块出问题导致的。最近在重构一个大型项目,借此机会和大家聊聊ORM。

ORM(Object Relational Mapping)对象关系映射,是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换,简单点说就是将数据库里面的一条数据映射成一个对象,要对某条数据增删改查时直接操作对应的对象即可。这样带来的好处是不言而喻的,比如要insert一条记录,原始的做法是这样:

INSERT INTO `user` (`id`, `account`, `password`) 
VALUES (1, 'it2048', '123456');

这样做会有一些问题:

  1. 手写SQL很费时,遇到几十上百个字段的表,一句insert要耗费半天精力。
  2. 每次都要看着数据库客户端,不然属性名称没法写。
  3. 容易把字段的类型弄错,varchar类型的属性传入了int。
  4. 容易写出SQL注入漏洞。

为了解决这些问题,ORM顺势而生,使用ORM之后的代码如下:

<?php
$model = new User();
$model->id = 1;
$model->account = 'it2048';
$model->password = '123456'; 
$model->save();

对比一下会发现,使用ORM之后上面那些问题都迎刃而解,接下来看看他是如何实现的。

比如MySQL里面的User表如下:

id account password
1 it2048 123456

对应的ORM如下:

<?php
class User extends Model{
    
    protected $id;
    protected $account;
    protected $password;
    
    public function setAccount($account){
        $this->account = $account;
    }
    public function getAccount($account){
        return $this->account;
    }
}

需要插入一条记录只需要new一个User类,然后操作User对象给属性赋值,最后调用save()方法将User对象转换成insert语存储到MySQL。大部分操作都可以在父类Model中封装,比如save()方法,这就是ActiveRecord(ORM的一种思想)的实现方式。

一. ORM的两种实现哲学

我们将ORM的思想拆分之后会发现它就两个功能。

  1. 数据操作 - 对数据对象做变更,就是我们常说的业务逻辑。

  2. 数据持久化 - 将数据落地,比如存储到MySQL,MongoDB等不同的数据库。

有两个功能,就有了吵架的理由了。于是大家分成了两派,一派认为应该把两个功能合在一起,简单方便,易上手,名字都想好了就叫 ActiveRecord。另一派认为两个功能必须分开,扩展灵活,逼格高,名字也想好了就叫 Data Mappers。两派高手吵了很久,Talk is cheap,Show me the code ,咱们github见。

二. ActiveRecord

从面向对象的角度来说,将数据操作与数据持久化两个功能放一起违反了单一功能原则。回顾一下什么是单一功能原则?每个类都应该有一个单一的功能,并且该功能应该由这个类完全封装起来。比如业务逻辑和存储逻辑是两个独立的模块,两者在功能上不依赖,如果把两个完全独立的功能封装在一起会导致代码耦合,这也是面向对象程序设计时要规避的。

话虽然这么说,但规定是死的,人是活的,在实际项目中又不一样了。ActiveRecord在实际项目中风驰电掣,发展迅猛,主流的编程框架基本都选择它作为ORM。用ActiveRecord ORM的PHP框架有Laravel, Yii, CodeIgniter, CakePHP等。其他语言用的有 Ruby on Rails,Django等。

ActiveRecord上手非常快,业务逻辑和持久化逻辑在一个对象里一起解决,封装越好的框架持久化逻辑对编程人员越透明,程序员甚至不用知道底层数据库使用的是MySQL还是MongoDB。

看一个调用实例:

<?php
$model = new User();
$model->id = 1;
$model->account = 'it2048';
$model->password = '123456'; 
$model->save();

$model 属性的修改属于业务逻辑,调用save()方法属于持久化逻辑。使用者完全不用关心save()方法执行后数据是存储到MySQL还是MongoDB,在开发过程中可以将精力全部放到业务逻辑,开发速度非常快。

三. Data Mappers

从面向对象的角度来说,将数据操作与数据持久化两个功能分开符合单一功能原则。这样设计出来的代码低耦合,扩展性强,性能有保证。对于理论派开发者来说Data Mappers肯定是首选。

但是在实际项目中Data Mappers的发展并不好,主要是出活慢。简单点说就是一个对象可以解决的事情,现在不得不用两个对象来解决,其中还有一个是全局对象(持久化逻辑)。对于代码的封装来说,全局对象的初始化和传递是大问题。初始化需要依赖框架,传递需要显示传递。这就导致我们封装的package不通用,只能在特定框架下传递特定对象才能使用。另一个问题是扩展性强就要求有大量的参数配置,开发者需要在代码层面关心具体用哪个数据库,怎样使用SQL语句性能好等,对开发者要求较高。

Data Mappers带来的好处主要体现在后期,比如需要优化性能,我们可以将一次请求中的所有SQL批量执行,这些SQL统一放在全局持久化对象中,很方便就能实现批量处理操作。这在ActiveRecord中很难做到。拿到持久化对象之后对数据的干预也会非常方便,例如MySQL表中的字段类型从枚举变成了int,在ActiveRecord中你需要查找所有代码,将该字段修正。而Data Mappers只需要在持久化对象中做个替换。

看一个调用实例:

<?php
$model = new User();
$model->setId(1);
$model->setAccount('it2048');
$model->setPassword('123456'); 

$entityManager = EntityManager::create($connection, $config);
$entityManager->persist($model);
//flush通常在请求结束后执行
$entityManager->flush();

$model 对象属性的修改属于业务逻辑,$entityManager对象涵括持久化逻辑。通常$entityManager对象是全局的,达到统一管理数据的目的。flush()save()方法类似,但flush()是对$entityManager中所有数据的存储,一般在请求结束时调用。

使用Data Mappers的框架数量相比ActiveRecord要少很多,主要有Java Hibernate,PHP Doctrine,SQLAlchemy in Python,EntityFramework for Microsoft .NET。

Data Mappers极大的增强了项目在ORM模块的扩展性,对在ORM模块踩过坑的开发者来说是一剂良药,但是良药苦口。

四. 如何选择ORM

上面把ActiveRecord和Data Mappers都介绍清楚了,选择哪一个需要根据实际业务需求来。如果是我的话,我会更多的考虑当前公司的发展情况,如果公司处于发展期,业务需求多,那肯定选择ActiveRecord,保证高产出最重要。如果公司处于技术沉淀期,比如开始还技术债,那就选择Data Mappers,一是可以沉淀很多技术,二是能将项目的性能与扩展性提升。

一般项目初期会选择ActiveRecord,如果项目比较成功,有一天发现ActiveRecord优化起来很吃力,要改造它的时候想到有Data Mappers,然后从ActiveRecord过度到Data Mappers,完成项目优化。这也是程序员正常的成长路径。架构被程序员开发,同样也会帮助程序员成长。

五. 参考文档

https://www.thoughtfulcode.com/orm-active-record-vs-data-mapper/

六. 最后

最近遇到很多"洗稿"的人,而且这些人的粉丝还非常多,更恶心的是这些人洗稿之后还反过来举报原创抄袭。对于原创来说打击太大了,我写文章不为挣钱,觉得不错的点个喜欢就是对作者最大的安慰。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容