架构简洁之道:从阿里开源应用架构COLA说起

1.png

导读:COLA 的主要目的是为应用架构提供一套简单的可以复制、可以理解、可以落地、可以控制复杂性的”指导和约束"。在实践中作者发现 COLA 在简洁性上仍有不足,因此给 COLA 做了一次“升级”,在这次升级中,没有增加任何新的功能,而是尽量多删减了一些概念和功能,让 COLA 更简洁有效。

最近,同事告诉我,COLA 作为应用架构,已经被选入阿里云的 Java 应用初始化的应用架构选项之一。

2.png

This is really something,于是,在这个里程碑节点上,我开始回过头来,重新审视 COLA 一路走来的得与失。

COLA 作为一种架构思想无疑是成功的。但是作为框架,个人感觉有点鸡肋之嫌。特别是在简洁性上做的不好,感觉做了不少画蛇添足的事情。

试想一下,有些功能我作为作者都很少去使用,我实在想不到,它为什么还有存在的理由。

基于上面的思考,我做了这一次 COLA 2.0 到 COLA 3.0 的升级。在本次升级中,我没有增加任何新的功能,而是尽量多删减了一些概念和功能。让 COLA 可以更加纯粹的 focus 在应用架构上,而不是框架支持和架构约束上。

支持我做这些决策的背后原因只有一个——奥卡姆剃刀原理。

奥卡姆剃刀原理

奥卡姆剃刀原理,是指如无必要,勿增实体(Entities should not be multiplied unnecessarily),即“简单有效原理”。正如奥卡姆在《箴言书注》2 卷 15 题说“切勿浪费较多东西去做,用较少的东西,同样可以做好的事情。”

在具体的应用过程中,我们可以遵循以下原则去做事情:

“如果同一个现象有 n 种理论,最简单的那个便是最正确的。能用 n 做好事情,那就不要有第 n+1 个动作。”

比如,《皇帝的新衣》的皇帝到底穿没穿衣服呢?如果你在现场,你很有可能就是大臣之一。

如果懂得了奥卡姆剃刀原理,可以用逻辑手段,判断谁是真理。

  • 第一种逻辑如下:假设皇帝是真的穿了衣服→假设愚蠢的人看不见→假设你就是愚蠢的人→所以你没看见皇帝穿衣服;
  • 第二种逻辑如下:假设皇帝没穿衣服→所以你没看见皇帝穿衣服。

同样看见光身子的皇帝,第二种解释简单明了。而第一种解释,可能正因为它是错误的,就需要更多假设来补救漏洞,就像说谎圆谎一样。

真相不需要伪装掩饰,简单明了。

再比如,地心说和日心说,托勒密的地心说模型是一个本轮均轮模型。人们可以按照这个模型,定量计算行星的运动,据此推测行星所在的位置。

到了中世纪后期随着观察仪器的不断改进,人们能够更加精确地测量出行星的位置和运动,观测到行星实际位置与这个模型的计算结果存在偏差,一开始还能勉强应付,后来小本轮增加到八十多个,却仍然不能精确地计算出行星的准确位置。

1543 年,波兰天文学家哥白尼在临终时发表了一部具有历史意义的著作——《天体运行论》。这个理论体系提出了一个明确的观点:太阳是宇宙的中心,一切行星都在围绕太阳旋转。该理论认为,地球也是行星之一,它一方面像陀螺一样自转,一方面又和其他行星一样围绕太阳转动。

3.png

哥白尼的计算不仅结构严谨,而且计算简单,与已经加到八十余个圈的地心说相比,哥白尼的计算与实际观测资料能更好地吻合。因此,地心说最终被日心说所取代。

设计中的弯弯绕

深入考察一下,我们系统中,类似于“地心说”这样的弯弯绕的设计,实在是不在少数。

从系统架构的角度看,有些弯弯绕是因为系统边界划分不合理,导致职责不清,依赖混乱。

从应用架构的角度看,有些弯弯绕是因为过度设计,为了追求所谓的灵活性和可扩展性,使用了不恰当的设计。导致本来可以直观呈现的代码逻辑,被各种包装,各种隐藏,各种转发.... 无形中极大的阻碍了代码的可读性和可理解性,增加了维护成本。

举个例子,我看过无数的业务系统,喜欢拿业务流程编排说事情。因此,在业务系统中,可以看到各种五花八门的“弯弯绕设计”。

比如,在一个业务系统中,我看到了如下的 pipeline 设计。这个设计的本质是说把一个复杂的业务操作进行结构化拆解为多个小的处理单元。

4.png

拆解是正确的,但是这种处理方式显然不够“奥卡姆”(关于更多结构化分解的内容,可以看我的另一篇文章:如何写复杂业务代码?)。作为维护人员,进入“入口函数”后,还要去查数据库,然后才能知道哪些组件被调用了,太绕了,不够直观,也不简洁。

同样的逻辑,按照下面的方式写不香吗?

public class CreateCSPUExecutor {@Resourceprivate InitContextStep initContextStep;@Resourceprivate CheckRequiredParamStep checkRequiredParamStep;@Resourceprivate CheckUnitStep checkUnitStep;@Resourceprivate CheckExpiringDateStep checkExpiringDateStep;@Resourceprivate CheckBarCodeStep checkBarCodeStep;@Resourceprivate CheckBarCodeImgStep checkBarCodeImgStep;@Resourceprivate CheckBrandCategoryStep checkBrandCategoryStep;@Resourceprivate CheckProductDetailStep checkProductDetailStep;@Resourceprivate CheckSpecImgStep checkSpecImgStep;@Resourceprivate CreateCSPUStep createCSPUStep;@Resourceprivate CreateCSPULogStep createCSPULogStep;@Resourceprivate SendCSPUCreatedEventStep sendCSPUCreatedEventStep;public Long create(MyCspuSaveParam myCspuSaveParam){SaveCSPUContext context = initContextStep.initContext(myCspuSaveParam);checkRequiredParamStep.check(context);checkUnitStep.check(context);checkExpiringDateStep.check(context);checkBarCodeStep.check(context);checkBarCodeImgStep.check(context);checkBrandCategoryStep.check(context);checkProductDetailStep.check(context);checkSpecImgStep.check(context);createCSPUStep.create(context);createCSPULogStep.log(context);sendCSPUCreatedEventStep.sendEvent(context);return context.getCspu().getId();}
}
public class CreateCSPUExecutor {@Resourceprivate InitContextStep initContextStep;@Resourceprivate CheckRequiredParamStep checkRequiredParamStep;@Resourceprivate CheckUnitStep checkUnitStep;@Resourceprivate CheckExpiringDateStep checkExpiringDateStep;@Resourceprivate CheckBarCodeStep checkBarCodeStep;@Resourceprivate CheckBarCodeImgStep checkBarCodeImgStep;@Resourceprivate CheckBrandCategoryStep checkBrandCategoryStep;@Resourceprivate CheckProductDetailStep checkProductDetailStep;@Resourceprivate CheckSpecImgStep checkSpecImgStep;@Resourceprivate CreateCSPUStep createCSPUStep;@Resourceprivate CreateCSPULogStep createCSPULogStep;@Resourceprivate SendCSPUCreatedEventStep sendCSPUCreatedEventStep;public Long create(MyCspuSaveParam myCspuSaveParam){SaveCSPUContext context = initContextStep.initContext(myCspuSaveParam);checkRequiredParamStep.check(context);checkUnitStep.check(context);checkExpiringDateStep.check(context);checkBarCodeStep.check(context);checkBarCodeImgStep.check(context);checkBrandCategoryStep.check(context);checkProductDetailStep.check(context);checkSpecImgStep.check(context);createCSPUStep.create(context);createCSPULogStep.log(context);sendCSPUCreatedEventStep.sendEvent(context);return context.getCspu().getId();}
}

这种写法简单直观,易维护,与前一种方式相比,具有同样的组件复用性。符合奥卡姆剃刀的精神,相比较而言,前面那种弯弯绕设计,虽然看起来有点设计感,带来了一点点 OCP 的好处。但是无端增加了理解和认知成本,孰优孰劣,不难分辨。

COLA 3.0 升级

做了这么长的铺垫,终于到了批斗 COLA 中“弯弯绕设计”的时候了。

1. 去掉 Command

在 COLA 的初始阶段,因为受到 CQRS 的影响,于是想到了使用命令模式来处理用户请求。设计的初衷是想通过框架,一方面强制约束 Command 和 Query 的处理方式,另一方面把 Service 里面的逻辑,强制拆分到 CommandExecutor 中去,防止 Service 膨胀过快。

和上面介绍过的 pipeline 设计类似,这种设计有点绕,不够直观,如下所示:

public class MetricsServiceImpl implements MetricsServiceI{@Autowiredprivate CommandBusI commandBus;@Overridepublic Response addATAMetric(ATAMetricAddCmd cmd) {return commandBus.send(cmd);}@Overridepublic Response addSharingMetric(SharingMetricAddCmd cmd) {return commandBus.send(cmd);}@Overridepublic Response addPatentMetric(PatentMetricAddCmd cmd) {return  commandBus.send(cmd);}@Overridepublic Response addPaperMetric(PaperMetricAddCmd cmd) {return  commandBus.send(cmd);}
}

看起来还挺干净的,可是 ATAMetricAddCmd 到底是被哪个 Executor 处理的呢,不直观。我还要去理解 CommandBus,以及 CommandBus 是如何注册 Executor 的。无形中增加了认知成本,不好。

既然这样,为何不用奥卡姆剃刀把这个 CommandBus 剔除呢。如下所示,去除 CommandBus 之后,代码是不是直观了很多,唯一的损失是我们会失去框架层面提供的 Interceptor 功能,然而,Interceptor 正是我下一个要动刀的地方。

public class MetricsServiceImpl implements MetricsServiceI{@Resourceprivate ATAMetricAddCmdExe ataMetricAddCmdExe;@Resourceprivate SharingMetricAddCmdExe sharingMetricAddCmdExe;@Resourceprivate PatentMetricAddCmdExe patentMetricAddCmdExe;@Resourceprivate PaperMetricAddCmdExe paperMetricAddCmdExe;@Overridepublic Response addATAMetric(ATAMetricAddCmd cmd) {return ataMetricAddCmdExe.execute(cmd);}@Overridepublic Response addSharingMetric(SharingMetricAddCmd cmd) {return sharingMetricAddCmdExe.execute(cmd);}@Overridepublic Response addPatentMetric(PatentMetricAddCmd cmd) {return  patentMetricAddCmdExe.execute(cmd);}@Overridepublic Response addPaperMetric(PaperMetricAddCmd cmd) {return  paperMetricAddCmdExe.execute(cmd);}
}

2. 去掉 Interceptor

当时设计 Interceptor,是因为有 CommandBus 作为基础,为了更好的利用命令模式带来的好处,便添加了 Interceptor 功能。其本质是一个 AOP 处理。

鉴于 Spring 的 AOP 功能已经很完善了,这个设计也是有点鸡肋。事实证明,大家在使用 COLA 框架的时候,很少会使用 Interceptor,包括我自己也是一样。既然如此,剔除也罢。

3. 去掉 Convertor、Validator、Assembler

关于命名的重要性,这里就不赘述了。当时想着是否能从框架层面,规范一下一些常用功能的命名。但是在实际使用中,发现这个想法也是有些过于理想化了。

我记得,在团队实践 COLA 的初期,还经常为什么是 Convertor(转换器),什么是 Assembler(组装器)的事情,争论不休。

后面我仔细想了想,命名虽然很重要,但其作用域最多也就是一个团队规范,你校验器是叫 Validator 还是 Checker 并没有什么本质区别,团队自己定义就好了。尝试从框架层面去解决团队约定问题,其效果不会太好,因此也果断挥刀剔除。

4. 类扫描优化

业务身份和扩展点的思想,是 TMF 的核心理念,也是阿里业务中台的进行多业务支持的核心方法论。

COLA 致力于提供一种轻量级的扩展实现方式,因此该功能在奥卡姆的屠刀下得以保存。因为 COLA 的扩展点设计是借鉴了中台的 TMF,因此在前面的设计中,其类扫描方案是直接照搬 TMF 的做法。

实际上,TMF 的类扫描方案对 COLA 来说有点多余。因为 COLA 本身就是架设在 Spring 的基础之上,而 Spring 又是建立在类扫描的基础之上。因此,我们完全可以复用 Spring 的类扫描,没必要自己写一套。

在原生的 Spring 中,至少有 3 种方式可以获取到用户自定义 Annotation 的 Bean,最简洁的是通过 ListableBeanFactory.getBeansWithAnnotation 方法,或者使用 ClassPathScanningCandidateComponentProvider 进行扫包。

在这次改版中,我选用的是 getBeansWithAnnotation 方法,主要是为了获取 @Extension 的 Bean,用来实现扩展点功能,废弃了原来的 TMF 类扫描实现。

总结

触发这次升级的动机,主要是因为,自己在实践 COLA 的过程中,的确发现有些华而不实的功能。在 COLA 作为阿里云的基础应用架构,其影响力越来越大的时候,我有责任给到大家一个正确的引导——去伪存真,简洁有效,而不是引入更多的复杂度。

实际上,COLA 是由两部分组成的:

一方面 COLA 是一种架构思想,是整合了洋葱圈架构、适配器架构、DDD、整洁架构、TMF 等架构思想的一种应用架构。

5.png

在这次升级中,架构思想部分基本没有变化,唯一一点是因为去除了 Command 概念,因此 CQRS 也成了可选项,而不再是一种强要求。

另一方面 COLA 也是框架组件,通过这次升级,我使用奥卡姆剃刀砍掉了绝大部分的组件能力,仅仅保留了扩展点功能。其用意是不希望 COLA 作为框架给到应用开发者太多的约束,这不符合简单有效的风格。

所以,总结下来,与其说这是一次升级,不如说它是功能“降级”,是在做减法。

但我相信,减法可以让 COLA 更加符合奥卡姆精神,帮助 COLA 轻装上阵,走的更远。

COLA 开源地址:https://github.com/alibaba/COLA

阿里云 JAVA 应用脚手架

start.aliyun.com 是基于 Spring-initializr 实现的工程脚手架生成平台,开发者们只需要添加一些注解和少量配置,就可以快速搭建分布式应用系统,它使用更亲切的中文,也不会有网络延迟问题,最重要的是提供更多本地化的组件依赖。

点击链接,立即体验阿里云 JAVA 应用脚手架:https://start.aliyun.com/?utm_content=g_1000150531

“阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/515484.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html、css、js、react、vue 文字一行一行显示出来

前端时间在做 年报,就不难涉及到 年报 具有的几大特性: 1、页面滑动特效 2、文字一行一行出现特效 3、页面内动画 等等 这片文章主要展示一下 文字一行一行出现特效 代码(react 为例) 先看效果图: 第一步&#xff…

哪家互联网公司涨薪最厉害?居然不是阿里腾讯

最近脉脉职言区有一条讨论火了:哪家互联网公司涨薪最厉害?按照拼多多员工的说法,应届毕业生可以拿比腾讯阿里高30%的薪资,而有工作经验的员工普遍薪资水平也高出业内30%~50%以上。而且在去年由于疫情众多企业降薪、甚至裁员的状况…

MongoDB 安装与配置~linux

文章目录一、 安装建议二、 安装步骤2.1. 下载2.2. 解压缩2.3. 重命名2.4. 配置环境变量2.5. 使其生效2.6. 配置MongoDB2.7. 启动MongoDB2.8. 网络安全组一、 安装建议 MongoDB 可以在mac/win/linux上安装,我个人建议在linux上安装会更好,这样测试起来更…

节省服务器成本50%以上!独角兽完美日记电商系统容器化改造实践

完美日记创立于2017年,这家公司上线不到两年即成为天猫彩妆销冠,2019年成为11年来第一个登上天猫双十一彩妆榜首的国货品牌,包揽天猫2019全年彩妆销冠;2020年4月成为首个亮相天猫超级品牌日的国货彩妆品牌,同时勇破彩妆…

浏览器从输入URL到页面渲染过程 —— 浏览器的进程与线程

之前我有总结过一篇经典面试题:浏览器从输入URL到页面渲染过程,接下里我将对某些知识点进行更细致的解析。 浏览器从输入URL到页面渲染过程 系列文章: (二):浏览器从输入URL到页面渲染过程 ——页面渲染流…

MongoDB 的可视化管理工具~连接腾讯云MongoDB服务

不论是mysql或者redis或者es,我们都会使用远程的客户端工具来连接数据库server,那么目前的linux上锁安装的MongoDB就是server端,我们需要有一个客户端来进行可视化的管理,常用的可以使用Navcat来操作,当然使用其他的GU…

云原生时代业务架构的变革:从单体迈向Serverless

如今,各行各业都在谈数字化转型,尤其是新零售、传媒、交通等行业。数字化的商业形态已经成为主流,逐渐替代了传统的商业形态。在另外一些行业里(如工业制造),虽然企业的商业形态并非以数字化的形式表现&…

冯诺依曼架构的 IO 鸿沟,谁能来填补?

作者 | 宋慧头图 | 下载于视觉中国随着AI技术、数据分析等领域兴起,数据变得越来越重要了,数据处理往往需要用到大量的内存,数据量爆发式增长让各种内存密集型应用层出不穷,如Redis数据库、SAP HANA企业核心系统。在CSDN 2019、20…

使用Git后10件你可能需要“反悔”的事

云栖号资讯:【点击查看更多行业资讯】 在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! Git是目前世界上最优秀最流行的分布式版本控制系统,也是程序员们日常使用最频繁的工具之一(几乎每天都…

SpringBoot 整合MongoDB

文章目录1. 引入依赖2. 在yml中添加配置3. 在启动类中开启mongodb在model中添加依赖&#xff0c;为什么在model中添加&#xff1f;因为会有映射的实体类MongoObject对应mongodb中的数据表对象&#xff1a;1. 引入依赖 <dependency><groupId>org.springframework.b…

浏览器从输入URL到页面渲染过程 ——页面渲染流程

之前我有总结过一篇经典面试题&#xff1a;浏览器从输入URL到页面渲染过程 &#xff0c;接下里我将对某些知识点进行更细致的解析。 浏览器从输入URL到页面渲染过程 系列文章&#xff1a; &#xff08;一&#xff09;&#xff1a;浏览器从输入URL到页面渲染过程 —— 浏览器的…

阿里发布2020农产品电商报告数字农业将成风口

完美日记创立于2017年&#xff0c;这家公司上线不到两年即成为天猫彩妆销冠&#xff0c;2019年成为11年来第一个登上天猫双十一彩妆榜首的国货品牌&#xff0c;包揽天猫2019全年彩妆销冠&#xff1b;2020年4月成为首个亮相天猫超级品牌日的国货彩妆品牌&#xff0c;同时勇破彩妆…

【干货】和你谈谈数据分析报告

前言&#xff1a; -更多关于数智化转型、数据中台内容请加入阿里云数据中台交流群—数智俱乐部 &#xff08;文末扫描二维码或点此加入&#xff09; -阿里云数据中台官网 https://dp.alibaba.com/index &#xff08;作者&#xff1a;数智从业者&#xff09; 在当今企业纷纷推动…

Vue3 安装axios使用报错:Uncaught TypeError: Cannot read property ‘use‘ of undefined

最近在学习Vue3&#xff0c;使用vue cli4搭建了一个demo项目&#xff0c;安装axios后&#xff0c;控制台报错&#xff1a; Uncaught TypeError: Cannot read property use of undefinedat eval (axios.js?be3b:59)at Module../src/plugins/axios.js (app.js:1229)at __webpac…

云原生五大趋势预测,K8s安卓化位列其一

作者 | 李响、张磊 Kubernetes 本身并不直接产生商业价值&#xff0c;你不会花钱去购买 Kubernetes 。这就跟安卓一样&#xff0c;你不会直接掏钱去买一个安卓系统。Kubernetes 真正产生价值的地方也在于它的上层应用生态。 “未来的软件一定是生长于云上的”&#xff0c;这是…

centos7 linux 安装 keeplived

文章目录一、简介部署总览1. 简介2. 部署总览二、安装实战2.1. 安装环境2.2. 下载软件包2.3. 同步软件包2.4. 解压、编译、安装三、系统服务3.1. 配置复制3.2. 脚本复制3.3. 服务开机启动四、配置修改4.1. 编辑配置4.2. 140 服务器配置4.3. 141 服务器配置4.4. 启动keepalived4…

阿里人脸识别安全技术获专利可防范3D人脸面具攻击

云栖号资讯&#xff1a;【点击查看更多行业资讯】 在这里您可以找到不同行业的第一手的上云资讯&#xff0c;还在等什么&#xff0c;快来&#xff01; 当人脸识别遇上3D技术&#xff0c;谁将是最后的胜利者?长期困扰人脸识别行业的安全问题有了最新的解法。日前&#xff0c;阿…

vue3中 使用 swiper 插件,自定义切换按钮, 将 前进后退 、左右切换 按钮放到容器外部

今天在使用 swiper 时&#xff0c;遇到一个 坑爹的 问题。 swiper 组件的本来样式长这样&#xff1a; 左右切换的按钮在滑动容器内部&#xff0c;但是我们想要它跑到容器外面去。 网上找了一堆方法都不好使 也不知道是不是因为 我使用的是 高版本原因 最终实验结果&#x…

一眼看尽5G江湖,Gartner发布5G网络基础设施魔力象限报告

作者 | 白告天来源 | 边缘计算社区头图 | 下载于视觉中国近日&#xff0c;国际权威分析机构Gartner发布了首份《通信运营商5G网络基础设施魔力象限》报告。以最直观的方式展示了这一领域内各家头部企业的力量对比情况。Gartner通信运营商5G网络基础设施魔力象限该魔力象限帮助通…

一篇小文带你走进RabbitMQ的世界

云栖号资讯&#xff1a;【点击查看更多行业资讯】 在这里您可以找到不同行业的第一手的上云资讯&#xff0c;还在等什么&#xff0c;快来&#xff01; 说到消息中间件&#xff0c;大部分人的第一印象可能是Kafka。毕竟Kafka自问世以来&#xff0c;就顶着高并发&#xff0c;大流…