阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

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

  • 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:002551

评论

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

快速上手vs2019

Loken

音视频 5月月更

LR.Net低代码开发平台 快速设计权限管理模块

力软低代码开发平台

第一次读 “Clean” 系列,并没有觉得这是一本多好的书

博文视点Broadview

大数据培训实时数仓实践以及架构的演进

@零度

数仓 大数据开发

详解GaussDB(DWS)的CPU资源隔离管控能力

华为云开发者联盟

数据库 cpu GaussDB 资源管控

OpenYurt v0.7.0 版本解读:无侵入的跨网络域解决方案 Raven

阿里巴巴云原生

阿里云 开源 云原生 边缘计算 openyurt

开发技术指南 | 最全 Substrate 与 Polkadot 技术文档、教程、课程

One Block Community

区块链 技术 波卡生态

2022年第1季度中国跨境进口零售电商市场规模为983.1亿元

易观分析

跨境电商

Serverless JOB | 传统任务新变革

Serverless Devs

Serverless SAE

墨天轮访谈 | 阿里云捷熙:AnalyticDB,人人可用的数据分析服务

墨天轮

数据库 阿里云 国产数据库

查询语句写了limit 1,为什么依然很慢?

华为云开发者联盟

后端 开发 计算引擎 limit 1

低代码构建物联网平台,让物联网项目更简单

AIRIOT

物联网 低代码开发

AI+工业互联网:百度AI专利讲述“中国智造”

百度开发者中心

解读ICDE'22论文:基于鲁棒和可解释自编码器的无监督时间序列离群点检测算法

华为云开发者联盟

数据挖掘 数据库 时间序列 自动编码器

陆奇:“黑客精神”过时了吗?答案是永远不会

图灵教育

nginx 程序员 服务器 计算机

架构实战营 第 6 期 模块七课后作业

火钳刘明

#架构实战营 「架构实战营」

GitHub 3.1K,业界首个流式语音合成系统开源!

百度开发者中心

Apache APISIX v2.14.1 探索性版本发布,进军更多领域

API7.ai 技术团队

服务注册与发现 API网关 Apache APISIX APISIX 网关

影视作品制作中不可或缺的技术支撑——云渲染技术

Finovy Cloud

服务器 云渲染 GPU算力

银行应将低代码能力作为在评估厂商综合能力的一个维度

易观分析

低代码 银行

【LeetCode】单词长度的最大乘积Java题解

Albert

LeetCode 5月月更

深入浅出Nginx实战与架构原理

C++后台开发

nginx 架构师 后端开发 Linux服务器开发 C++后台开发

Docker容器:将带UI的程序直接转为Web应用,so easy

华为云开发者联盟

云计算 后端 Docker容器 Web应用

手把手教你打造一套最牛的知识管理系统

小炮

2022云原生安全发展24个洞见

青藤云安全

网络安全 容器安全 镜像安全

全自动、可视化开发,成为敏捷转型落地关键词

SoFlu软件机器人

国内首个纯数字藏品元宇宙世界“ADAMeta”星城宇宙开启公测

最新动态

软件开发模型有哪些?

源字节1号

软件开发

phpstorm 配置 Xdebug 调试

CRMEB

昆仑芯科技加入龙蜥社区 ,赋能智慧开源,共筑AI芯生态

OpenAnolis小助手

芯片 龙蜥社区 CLA 昆仑芯科技

技术分享| 快对讲调度系统设计概要

anyRTC开发者

音视频 语音通话 调度 视频通话 快对讲

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