Martin Fowler厌倦ORM了

12-05-09 banq
Martin fowler几年前曾经非常推崇ORM(对象/关系数据库映射框架),特别是Hibernate和Ruby的Active Record,现在他面对大家越来越多对ORM责难和怀疑。他写了这篇新的文章:ORM的厌倦(OrmHate),下面大概谈谈他这篇文章大意。

首先,MF为ORM带来的复杂性做了辩解,对象和关系数据库存在天然阻抗,试图调和总是带来复杂性。他也认为:The object/relational mapping problem is hard,试图在对象和关系数据库之间进行映射是很难的,因为你需要处理两种不同角度视图中的数据,一个是关系数据库中,还有一个是内存中in-memory,这两者常常是由ORM实现,这里一点和对象没有关系,准确地说,ORM实际是在进行内存(in-memeory)和关系数据库之间映射,内存数据结构比关系模型要更灵活,大多数人更愿意使用这种内存数据结构,然后将其再保存持久到数据库中。

这种映射要比我们想象复杂得多,因为你一旦在内存改变数据结构,就必须映射同步到另外一边,如果有多个操作同时并行修改数据库就更复杂,ORM得处理这种并发,这种情况下你不能依赖事务机制,因为当你摆弄内存中数据,你是不能hold住事务的(通常通过数据库锁hold住)。

那么有没有ORM的替代呢?

MF提出两个解决方案:MF认为Hibernate 和 Active Record 已经是一个膨胀化软件,他看到很多人开始做自己系统的ORM,这是非常厉害的,但是他们没有意识到已经掉入泥潭,MF认为对象和关系数据库的映射如同计算机科学中的越战。

虽然很多ORM框架如 iBatis, Hibernate, 和 Active Record做了很多努力解决了很多问题,但是关键问题还是存在,ORM能够解决80%-90%的问题,但是剩余的问题就必须理解ORM和关系数据库内部工作机制才能解决。

MF认为这实际就是正确使用ORM的方式,他以Ruby中Active Record创造者David Heinemeier Hansson的话为例子,ORM帮助你处理掉了大部分无聊垃圾代码,但也提供了一个管道和沙井,让你直接处理SQL语句。

但是人们常常抱怨,为了使自己的对象模型更适合ORM,屈就ORM,只能变得更加具有关系化,MF认为这是使用关系数据库的必然结果,你要买使你的内存模型更加关系化,要么复杂化你的映射代码,为了简化你的对象和关系数据库映射,你最好哦让你的领域模型更加具有关系化,但是并不意味着你得按关系模型设计领域模型。

ORM是复杂的,因为它得处理双向映射,如果只有单向映射,问题复杂性也许就会解决,前提是你感觉SQL不复杂,这就是CQRS(读写分离)。

17
banq
2012-05-09 10:08
MF提出避免ORM这些复杂性的两个替代解决方案:

1.使用内存关系模型,也就是直接使用SQL。

2.干脆不使用关系数据库,直接使用NoSQL。

个人认为这两种方案比较极端,还是CQRS方案更好,实际可以通过内存中对象化的领域模型来屏蔽掉SQL或NOSQL,无论是SQL或NoSQL,都是领域对象的持久化,当然批量查询读取专门走另外一条线,结合NOSQL+Hadoop可以实现大数据分析。

MF提出这两种解决方案,其实还是不想否定ORM作用,将ORM复杂性归结为关系模型,其实纯粹的关系模式是很干净简单的,复杂就在于你把对象和关系搞在一起。

banq
2012-05-09 11:26
Twitter中有人认为:

devs abused ORM, using it as flat data access layer. Hibernate's overkill if you're not letting it handle relationships

开发者喜欢将ORM作为扁平数据访问层,(扁平数据是非结构化,非关系化),你如果不让Hibernate处理关系,实际它就没有什么用处。

ORM对DDD领域驱动设计的影响

[该贴被banq于2012-05-23 12:03修改过]

我2009年观点:

ORM已经是过去的事情

[该贴被banq于2012-05-31 09:04修改过]

flyzb
2012-05-09 23:16
ORM确实没什么用了。但我们不能否认业务对象的关联,在去年我采取了一种新的查询方式:不在数据库中关联(即坚决不使用join),而在内存中关联(可使用in)。因为这样导致查询缓存命中率奇高,所以系统性能大幅度提升。

gameboyLV
2012-05-10 09:33
我认为让ORM负责简单的CUD就好了,现在.NET有没有成熟的CQRS框架?

lostalien
2012-05-12 13:33
2012-05-09 23:16 "@flyzb"的内容
不在数据库中关联(即坚决不使用join),而在内存中关联(可使用in)。因为这样导致查询缓存命中率奇高,所以系统性能大幅度提升。 ...

这是什么意思呢?

flyzb
2012-05-13 00:46
首先要强调:领域对象之间是有关联的,所以关系查询是必然规律,不可违背。过去我们常常通过sql中的join来解决这种关系查询,而这恰恰是性能杀手。而“缓存”+“单表查询”一样能解决关系查询,性能还不差。

tomlin
2012-05-14 10:22
cqrs 难道不复杂吗?学习曲线太陡了,要掌握并应用于实践,指导实践,风险不是一般的大。这东西现在只能用于学习。离能真正在企业中应用还有一段很长的距离。

[该贴被tomlin于2012-05-14 10:28修改过]

tomlin
2012-05-14 10:32
2012-05-13 00:46 "@flyzb"的内容
首先要强调:领域对象之间是有关联的,所以关系查询是必然规律,不可违背。过去我们常常通过sql中的join来解决这种关系查询,而这恰恰是性能杀手。而“缓存”+“单表查询”一样能解决关系查询,性能还不差。 ...

关键问题是,这样做要付出的代价太大。我们在开发过程中,我们要写多少代码才能实现“join”的相关功能?所以你说的这种方案只能在关键业务上使用。

[该贴被tomlin于2012-05-14 10:36修改过]

banq
2012-05-14 11:43
2012-05-14 10:22 "@tomlin"的内容
cqrs 难道不复杂吗?学习曲线太陡了,要掌握并应用于实践 ...

CQRS实际是读写分离架构,这种架构在实践中已经广泛使用,本人接触过这样的系统,只不过大家没有意识到或者为之取名CQRS

老马这篇文章提出用NoSQL+CQRS避免ORM问题是切实可行的,联想到他前期推崇的文章:LMAX架构

这里还有一篇文章,虽然没有提CQRS名词,但是实际是一种读写架构分离:基于事件的NOSQL存储事务方案Transactional event-based NOSQL storage

主要图如下,我们的应用程序使用DAO实现读和写操作分离,这种架构使用ORM肯定不合算,因为ORM是读写合并在一起,致使读写两方面单独优化都因为涉及对方而不能发挥极致。

读写分离后,涉及两个数据库数据同步问题,这种同步机制实际在很多NOSQL内部比如MongoDB都有实现,就是启动一个定时同步线程程序job,进行读和写数据库的数据同步,那么这个job根据什么来同步呢?根据写操作的事件,也就是将每次写操作事件作为数据存储起来,然后Job去根据这些事件再对读数据库进行一次写操作,这种操作保证是幂等的即可,也就是可重复的。

BTW:上面我提到看到过实战中CQRS系统,他们是使用JGroup进行数据库同步的。

[该贴被banq于2012-05-14 13:36修改过]


banq
2012-05-16 10:52
2012-05-10 09:33 "@gameboyLV"的内容
我认为让ORM负责简单的CUD就好了,现在.NET有没有成熟的CQRS框架? ...

今天发现一篇文章适合.NET的CQRS:在微软WCF上实现CQRS-Implementing CORS support in WCF

使用ASP.NET的Web API实现CQRS-Implementing CORS support in ASP.NET Web APIs

xxooxx
2012-05-20 23:12
请问如下表结构:

公告表(公告id,主题,内容)

公告发布对象表(公告id,公告对象类型,公告对象id)

解释:公告对象类型是:个人,部门,角色。

请问如果页面需要显示一个公告列表,如果当前登录用户的id,所属部门id,所属角色id已知,那么不用显示这个登录用户可见公告列表呢?

你说的完全jion,根本不行!

gameboyLV
2012-05-21 09:05
怎么不行?公告发布对象表 放在缓存里,然后通过当前登录的用户信息可以获得可见公告的ID集合,然后用这个集合到数据库里查公告

xxooxx
2012-05-21 18:17
如果需要显示的公告列表,有查询条件呢?比如公告主题包括"招生”两个字。那么您说之前获取的公告id集合,就不对了,会包含不符合条件的公告,再考虑如果列表分页,在前端(相对数据库)去除不符合条件的id,那么将导致分页行数减少。

这才是真正产生无法不用join的原因啊。如果什么操作都可以分解到根据id获取对象,当然就不需要join了。试想一个mis系统,会有多少这种情况?

xxooxx
2012-05-21 22:21
简单点说吧,就是需要分页,难道你在前端对公告id进行分页。还是用

select limit 1-10 * from 公告表 in (公告id) 这种方式获取分页的公告列表?第二种的话,如果id列表是海量的呢?

猜你喜欢
2Go 1 2 下一页