NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

命令模式:若只如“初见”

  • 2018-01-03
  • 本文字数:2713 字

    阅读完需:约 9 分钟

似曾相识

最近在 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、分布式存储和领域驱动设计有丰富的实践经验,喜欢摄影和旅行。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2018-01-03 18:002559

评论

发布
暂无评论
发现更多内容

百度Apollo参编首批汽车信息安全国家标准,跻身车联网信息安全第一梯队

百度开发者中心

Apollo

百度智能云与雅量商业智能携手,加速零售行业智能化升级

百度开发者中心

百度智能云 零售行业

阿里云视频云,用技术普惠打造平民化“虚拟人”

阿里云视频云

人工智能 阿里云 视频云 数字人 虚拟人

Hexo个人博客快速部署到Gitee&Coding详细教程

老表

Hexo gitee CODING 博客配置 11月日更

GitHub远程免密连接详解,还顺手解决了RPC失败HTTP413

老表

GitHub RPC HTTP 11月日更

牛掰!“基础-中级-高级”Java程序员面试集结,看完献出我的膝盖

Java spring 程序员 JVM hashmap

科技热点周刊|ClickHouse 融资 2.5 亿美元、个人信息保护法正式实施、Facebook 改名 Meta

青云技术社区

云计算 facebook 云原生

百度鸿鹄芯片落地首款量产车吉利博越X 智能车机体验惊艳成都车展

百度开发者中心

百度 车联网 鸿鹄芯片

Github上线仅六天,收获Star超55K+,这套笔记能拿下90%以上面试

Java redis spring 程序员 架构

【高并发】开篇:线程与多线程

冰河

Java 并发编程 多线程 高并发 异步编程

科大讯飞联袂伯俊科技进军3C零售,构建发展新格局

科技热闻

RadonDB ClickHouse on K8s 2.1.0 发布!

RadonDB

数据库 Kubernetes Clickhouse RadonDB

你需要知道的 19 个 console 实用调试技巧

CRMEB

CSS JavaScript DOM console crmeb

杂谈—程序人生第一份工作

思想者杰克

程序人生 新手指南 程序

看完这篇SpringBoot让我在阿里成功涨薪40%,感谢

Java 编程 程序员 程序人生 springboot

行业白皮书发布!百度智慧城市助力城市“双碳”目标达成

百度开发者中心

人工智能 智慧城市

前后端、多语言、跨云部署,全链路追踪到底有多难?

阿里巴巴中间件

阿里云 云原生 中间件 全链路追踪

杂谈——程序人生我的大学

思想者杰克

linux lsquic 编译

webrtc developer

常用的Nmap脚本及使用实例

喀拉峻

网络安全 信息安全 渗透测试 脚本 nmap

端开发技术——5个高效的Flutter开发工具

思想者杰克

四种 AI 技术方案,教你拥有自己的 Avatar 形象

阿里云视频云

人工智能 阿里云 计算机视觉 视频云 元宇宙

百度智能云人脸采集SDK通过CFCA权威安全测评

百度开发者中心

安全 sdk

ReplacingMergeTree:实现Clickhouse数据更新

华为云开发者联盟

数据 事务 Clickhouse 数据更新 OLAP数据库

极客时间【架构实战营】第二期 模块七作业

Geek_91606e

架构实战营

接口文档工具yapi的安装

小鲍侃java

11月日更

万字长文解密数据异构最佳实践(含完整代码实现)!!

冰河

MySQL 数据库 canal 数据同步 数据异构

TDengine在浙商银行微服务监控中的实践

TDengine

tdengine 后端 时序数据库

架构实战营-总结

哈希

操作系统——计算机硬件简介

思想者杰克

2021年10月云主机性能评测报告

博睿数据

命令模式:若只如“初见”_架构_耿立超_InfoQ精选文章