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


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

似曾相识
设计模式

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

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

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

做为一个基于命令模式的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会绕过领域模型直接从持久层提取数据,这有助于提高性能,同时减轻领域模型的压力。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------