耿大侠 Diss国外架构师文章《From CQS to CQRS》

看到耿大侠发了一篇怼InfoQ上的一篇译文的技术文章,仔细拜读之后,收获良多,在征得原作者的同意后,把文章发在这里,供大家品鉴,滋取养分。

大多数程序开发人员一定拜读过设计模式的相关文献,想必,也一定在自己的日常工作中,套用个别设计模式优化过自己的局部代码,在局部代码的优化上,设计模式是死的,当你把设计模式放在更宽广的领域,设计模式会变成活的。本文解析了命令模式从面向对象设计过渡到领域驱动设计的思维过程,为您打开视野提供帮助。下面让我们一起看下耿大侠的文章吧。

似曾相识

最近在InfoQ上看到一篇谈论命令模式与CQRS架构的译文《From CQS to CQRS》(建议先阅读此文,本文会针对该文的一些观点进行探讨),文章从命令模式谈起,然后提出了命令模式的升级版——命令总线

这个图总给人一种似曾相识的感觉,仔细回想了一下,发觉这不正是Struts2架构中的核心部分吗?

作为一个基于命令模式的MVC框架,Struts2在对于命令的处理上和命令总线的设计如出一辙,它的ActionInvocationInterceptor对应的正是命令总线里CommandBusDecorator。应该说两者是因为遵循了同样的OO设计准则才得到了如此高度的一致,可见优雅的设计都是相似的,模式之所以成为模式是有必然性的。

命令模式“错”了吗?

关于命令模式的详细介绍可以参考四人帮的《设计模式》和《Head First Design Patterns》,本文不做过多赘述。命令模式的核心设计用意是对调用端和执行端进行解耦,这种解耦是非常彻底的,即:在调用端不会出现任何执行端的API,甚至在Command这个核心抽象上的execute方法上都是不带任何参数的:

public interface Command {
    public void execute();
}

这使得Command对于调用方而言完全是一个“黑盒”,调用方只知道下达命令,对于它将被如何执行毫不知情,命令的解释与执行是由命令和执行方共同完成的,这符合现实世界中很多事物之间的协作关系,确保了参与其中的各方职责单一,分工明确,否则角色混乱,各种事情搅在一起就会变成一团糟。

《From CQS to CQRS》一文为引出命令总线在介绍命令模式时阐述了它的一个"缺陷":即由于命令模式的“强”封装使得它不能很好地“包裹”数据,也就是命令参数。文章称这些经常变化的命令参数既不能通过execute方法传递,又不适合作为命令类构造函数的参数,因此作者认为命令模式是“有问题”的,需要进行重构,而重构的结果就是命令总线。

然而文章对命令模式的diss是站不住脚的,因为即使按照作者推荐的方式将所谓变化的部分(即“数据”)抽离到一个DTO中,在调用端依然需要实例化它,为其设定各种参数,这些参数天然就是执行业务的前提,无论采用何种设计,作为下达命令的一方“把要干的事情讲明白”是最起码的“份内事”,所以在命令类的构造函数上传递参数和剥离到一个单独的DTO中包裹数据没有任何本质的区别,后者的做法反而有“从富领域模型向贫血的领域模型开倒车”的嫌疑。

所以命令模式并没有错,命令总线也不是为解决命令模式所谓的“弊端”而来,它实际上是应更大的架构目标和应用场景而产生的。

更大的格局

原生的命令模式在它所适用的场景上表现自然是完美的,这些场景大多数是领域模型的一些“局部”,命令的类型和逻辑都是和业务紧密联系的。而另一方面,人们也认识到命令模式具有广泛的适用性,具备在更高级别的架构模式中扮演核心角色的能力,但是将命令模式提升到更加通用和完备的层面还需要解决以下一些问题:

1. 将命令的“数据”和“逻辑”剥离开,形成通用的“命令”和“命令处理机制”

在原生的命令模式里,每一个具体的命令类都会包含特定的字段和逻辑,通用化处理的第一步就需要把命令的数据和行为剥离开,数据剥离之后可以使用通用的数据结构如Map或更加抽象的类型如Object来替换,而行为上的通用化处理则要依靠下面几点来实现。

2. 抽象统一的命令处理流程

在一个特定的框架或业务系统里,命令的执行往往都有一定的“套路”,如果想让命令的执行通用化,势必要精心地总结和归纳各种命令在执行上的共性,提炼出一个通用的程序执行的“流程”,这个所谓的“流程”就是服务总线模式中的CommandBus和Struts2中的ActionInvocation,统一处理流程可以包含大量丰富的主题,比如日志、事务处理、安全拦截、性能跟踪、数据校验等等。

3. 基于配置的流程定义与组装

但是统一的处理流程并不意味着只能有一种,也不意味着一成不变,为了让流程处理具有广泛的适用性,通过配置的方式去定义和组装命令的处理流程是非常必要的,这样可以让流程变得灵活,可定制,流程中的环节也都是可插拔的,就如同Struts2使用struts.xml去描述interceptors栈和action那样。

4. 提供命令处理的公共基础设施

当统一的“流程”抽象出来之后,需要针对普遍存在的“环节”提供公共实现,例如前文命令总线上示意的LoggingDecoratorValidationDecorator等一系列的装饰器和Struts2中的loggervalidation等一系列的Interceptor,这些都会作为命令处理过程中的“公共基础设施”,一环一环地套接起来,让每一个命令逐一经过这些“环节”进行相应的处理。这种工作模式和面向切面编程中的“Around Advice”机制是完全一致的。

5. 给自定义命令处理逻辑留下接口

无论如何,这处理流程上的最后一环必定是留给命令“执行者”的,连同封装好的数据一起,落脚到一个回调的接口上,让命令“执行者”们补上属于它们的应尽之责:业务处理代码,则整个命令处理流程的“闭环”就算大功告成了。

原生的命令模式往往应用在领域模型上,与业务紧密关联,而命令总线的意图则是试图将命令模式提升到架构层面,在整个系统的某些“分层”(layer)之间建立一种一致的全局的通信模式,从而实现“层间解耦”,例如像Struts2那样在MVC的视图层与模型层之间组织和传递Action。为了实现这一目标,势必要对原生的命令模式进行改进,甚至是妥协,比如将命令的数据与行为进行拆分,这确实像是“从富领域模型向贫血的领域模型开倒车” ,但是为了实现更大的架构目标,局部的妥协是必须的,也是值得的。

终极产物

如从一条小溪最终汇入江河大海,命令模式被提升为命令总线之后进而又参与到了CQRS架构中,成为组成这一先进架构的核心模式之一,这也可以视为命令模式进化到现在的“终极产物”。CQRS架构的核心思想是把系统和外界的信息交换进行了读写分离,在数据写入时,通过构建富领域模型进行业务计算,这是领域驱动设计擅长的领域,在这个过程中“命令”是驱动领域模型运转的钥匙。在数据读取时,CQRS会绕过领域模型直接从持久层提取数据,这有助于提升性能,同时减轻领域模型的压力。

但是CQRS已经不再是本文关注的重点了,因为CQRS直接复用了命令总线,没有做其他的提升,本文写作的主要目的是想回顾命令模式从起源到终极产物的演化历程,阐述这些演化背后的真正用意以及实现这些目标的宝贵设计思想。

关于作者:耿立超,架构师,CSDN博客专家,博客http://blog.csdn.net/bluishglc 已从事多年大数据领域的研发工作,对企业级应用架构、SaaS、分布式存储和领域驱动设计有丰富的实践经验,喜欢摄影和旅行。

原文发布于微信公众号 - AI启蒙研究院(AIEvolve)

原文发表时间:2018-01-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阮一峰的网络日志

USENET简介

普通的互联网用户,可能对USENET知之甚少,或者根本就没有听说过它。但是,这是一种很重要的网络应用,里面有一些真正有趣的东西。 我在网上没有找到比较通俗易懂的...

2819
来自专栏Debian社区

Linux:为什么那么多人讨厌 Systemd

Systemd在Linux社区引起了无限争议。一些Linux用户对systemd的反对绝不屈从,还有一些Linux用户对systemd喜爱有加,还有很多人满不在...

1032
来自专栏北京马哥教育

Nginx与httpd对比

作为一个运维的学习者,对nginx和apache了解的很浅,但是作为以后运维过程中非常重要的两款服务器软件,静态web服务提供者,还是相当有必要深入的了解一下他...

3965
来自专栏地方网络工作室的专栏

Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(一)基础知识概述

Vue2+VueRouter2+Webpack+Axios 构建项目实战2017重制版(一)基础知识概述 前言 2016年,我写了一系列的 VUE 入门教程,当...

2169
来自专栏Python小白进阶之旅

Android离Linux越来越遥远了,Google的Android真的是开源的吗?

2879
来自专栏帘卷西风的专栏

开源CEGUI编辑器之一(MFC重写的LayoutEditor)

转载请注明出处:帘卷西风的专栏(http://blog.csdn.net/ljxfblog)

1212
来自专栏优惠券

腾讯云优惠券2860元续费/升级大礼包

推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。

6510
来自专栏Java技术栈

阿里巴巴,排行前10的开源项目!

1、FastDFS FastDFS是一个开源的分布式文件系统,她对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储...

4608
来自专栏Java学习网

最熟悉又陌生的5大概念之“JRE、JDK、J2SE、J2EE、J2ME”

看完标题,大家可能会有疑问,什么叫最熟悉又陌生的概念;说最熟悉是因为这些概念经常听到或看到,陌生是因为不了解它们的真正含义,怎么用,什么时候会用到;如果你觉得这...

3513
来自专栏大数据文摘

爬虫还在用Python?我与Node.js不得不说的故事

2.8K4

扫码关注云+社区