原文 http://www.jdon.com/jivejdon/thread/37891
命令查询的责任分离Command Query Responsibility Segregation (简称CQRS)模式是一种架构体系模式,能够使改变模型的状态的命令和模型状态的查询实现分离。这属于DDD 应用领域的一个模式,主要解决DDD 在数据库报表输出上处理方式。
来自Rethinking architecture with CQRS 一文对CQRS进行详细描述。
很多应用都需要持久层保存状态,因为这些状态不能丢失,如果只是放在内存中,而不是持久到磁盘上。状态是在领域模型中,每当模型保存到数据库以后,会有更多复杂的查询,都是使用复杂且慢的SQL语句实现的。比如如下复杂SQL语句:
queryBuilder.append( "m.*, " + "m.origin_participant_id as message_origin_participant_id, " + "po.first_name as message_origin_participant_first_name, " + "po.avatar as message_origin_participant_avatar, " + "po_ua.username as message_origin_participant_username, " + "CASE WHEN !isnull(po.fieldworker_project_id) THEN 'fieldworker' WHEN !isnull(po_ap.project_id) THEN 'fundraiser' ELSE 'player' END AS message_origin_participant_type, " + "po.city as message_origin_participant_city, " + "c.name as message_origin_participant_country, " + "pd.first_name as message_destination_participant_first_name, " + "pd.avatar as message_destination_participant_avatar, " + "pd_ua.username as message_destination_participant_username, " + "CASE WHEN !isnull(pd.fieldworker_project_id) THEN 'fieldworker' WHEN !isnull(pd_ap.project_id) THEN 'fundraiser' ELSE 'player' END AS message_destination_participant_type, " + "m.destination_participant_id as message_destination_participant_id " + "from internal_message m " + "left join player po on m.origin_participant_id = po.id " + "left join (select player_id, project_id from ambassador_project where enabled = true group by player_id) po_ap on po_ap.player_id = po.id " + "left join user_account po_ua on po.user_account_id = po_ua.id " + "left join country c on po.country_id = c.id " + "left join player pd on m.destination_participant_id = pd.id " + "left join (select player_id, project_id from ambassador_project where enabled = true group by player_id) pd_ap on pd_ap.player_id = pd.id " + "inner join user_account pd_ua on pd.user_account_id = pd_ua.id " + "where m.destination_participant_id = ? " ); |
SELECT * FROM messages WHERE receiving_participant = ? |
这篇文章其实提出了一个颠覆性的概念:分布式系统之间传输事件,把事件作为数据进行分布,当然也可以把事件作为key-value存储的数据。
目前EJB 标准JavaEE6等都明显落后了,透明化out-of-box分布式架构是一个趋势。
这个概念和EJB 提 出的将实体数据在服务器之间传输完全相反,所以,EJB才积极倡导失血模型,就是模型没有方法(只有setter/getter方法),只有字段数据,没 有行为,就是不能太胖,否则序列化性能就很差,但是没有行为的模型能称为对象模型吗?就像人失去血液能活吗?所以,失血模型是严重违背OO 的,但是因为EJB 把持着实现市场,所以,连同关系数据库编程思路,误导了一代程序员。
Spring 作为另外一个实现市场,虽然Spring 3.0出来了,但是由于其只对组件技术架构实现支持,对模型实体不闻不问(由Hibernate掌管),没有解决模型事件和技术组件架构之间的通讯联系方 式,结果,导致大部分程序员使用SSH或S2SH时,只是由服务触发模型,模型无法再触发其他服务或组件(模型最后成为事件最后终点站,这也是失血模型的 一个特点,数据是被驱动的,但是模型对象不是数据,模型对象有行为,必须可以发出事件驱动其他),或者将服务或组件直接注射到模型中,导致业务模型和技术 架构多点耦合。
以上矛盾也体现了EDA架构和所谓SOA架构的冲突所在。
作为一个好的框架,不对模型进行in-memory支持,而是通过Hibernate的二级缓存 ehcache绕道实现,这是当前Spring+Hibernate(SSH或S2SH)的最大问题所在。另外Hibernate其实是将领域模型和关系数据库进行捆绑,反而让领域模型丧失了其为需求服务的自由,比如异步 加载,即用即取lazy load等等。
所以,从DDD 要求出发,一定要有一个综合将实体模型和服务组件技术架构合起来考虑的框架,比如Qi4j或Jdonframework ,Spring + Hibernate这种分离思路其实是沿袭了EJB 的Session Bean + Entity Bean/JPA思路,这种思路离DDD 要求实在太远,可以看成是模型的汇编语言了。
hibernate JPA等ORM框架由于将关系数据表和实体对象绑定,如果我们直接将这个实体对象作为领域模型的实体(如果不是,系统中有两种实体对象,增加复杂性),那么无疑无法进行查询和命令(比如模型的修改)的分离,也就是是反CQRS模式。
CQRS 和关系数据库的读写分离策略也是相似的,查询是读,而状态修改一般是写,CQRS允许我们为查询优化数据表结构,因为对象领域模型的存储,数据表结构是无 所谓的,但是如果你使用Hibernate/JPA等ORM框架,就很难实现为查询优化数据表结构,因为ORM框架绑定领域模型和数据表。
这里是一个DDD 的Domain Driven Design PPT ,也谈到了CQRS,也称CQS架构,很不错。
下面这张图也是CQS架构图:
[该贴被banq于2009-12-29 11:35修改过]
这里有两种一致性,一个用户界面数据的一致性,还有一个是你说的数据库数据的一致性。
我们通常比较关心的是前者,至于你说的后者,技术上完全没有问题了,比如采取Key-values存储作为数据库即可。
关于是否有实践,你发言的这个论坛就是使用这种架构,因为Jdonframework 就是根据这种原理开发的。至于其他大型实践架构,实际可以参看NoSQL 实践,两者一致的,NOSQL大型实践案例包括EBay Facebook twitter linkedin等世界排名前茅的网站。
关于CQRS,是greg young通过实践提出的,见INFOQ他的访谈录 ,谈他的一个实践CQRS心得。
我简短翻译一下他的访谈录(顺便发牢骚:infoQ中国版编辑不知在做什么,这篇重要文章没有翻译?):
greg young当前工作在温哥华的IMIS, British Columbia. 我们开发一个算法交易系统,目前运行在全世界各大交易所,我当前是CTO,大部分时间是关注整体架构。
我们的领域模型是内存模型(in-memory model),代表市场中股票当前状态,我们所有业务策略都运行在这个领域模型中,我们选择领域驱动设计DDD 其实相当不寻常的,对于那些不熟悉的人来说,性能是第一,当你每秒处理20,000消息,...你通常关注可伸缩性 scalability, 低延迟low latency, 但是我们觉得代码的可理解性很重要,我们选择OO 编程可以让我们更加适应变化。
我去年QCon演讲过,我们的实践和经典理论的DDD 区别是:我们实体对象中封装的是可改变的状态,例如:volume-traded 命令是删除我们模型中的volume. 这其实产生一个事件,导致我们领域中的状态发生变化(banq注:典型的状态模式)
这 样非常有用,我们就不必在领域之外再进行状态持久保存,我们只有状态的切换,我们能获取这些状态切换事件,然后传到其他场景bounded contexts(DDD定义的绑定场景), 而在其他场景当前状态或当前状态的镜像能以不同方式建立和显示,这种情况很普遍,事务系统是以一种方式看数据,而报表系统则以另外不同方式看系统(命令查 询分离 CQRS)
相比围绕数据库的正常传统方式,他们就很象以一种非正常化的方式处理数据,通过将切换事件保存到事件流中,你就可以在 这第一个模型中做你任何传统正常化方式可以做的任何事情,在事件流的另外一边(监听者)有了第二个模型,它是以非正常化呈现,最好的方式是在他们之间建立 一个桥,这比通常同步性质的直接调用要好多,而且更加可伸缩性 。
下面是greg young关于DDD 实践的一些体会,太长,啃原文吧。