旅行网站的建设目录,电商网站建设推广,哪个网站可以做思维导图,网站 字体导读#xff1a;COLA 的主要目的是为应用架构提供一套简单的可以复制、可以理解、可以落地、可以控制复杂性的”指导和约束。在实践中作者发现 COLA 在简洁性上仍有不足#xff0c;因此给 COLA 做了一次“升级”#xff0c;在这次升级中#xff0c;没有增加任何新的功…
导读COLA 的主要目的是为应用架构提供一套简单的可以复制、可以理解、可以落地、可以控制复杂性的”指导和约束。在实践中作者发现 COLA 在简洁性上仍有不足因此给 COLA 做了一次“升级”在这次升级中没有增加任何新的功能而是尽量多删减了一些概念和功能让 COLA 更简洁有效。
最近同事告诉我COLA 作为应用架构已经被选入阿里云的 Java 应用初始化的应用架构选项之一。 This is really something于是在这个里程碑节点上我开始回过头来重新审视 COLA 一路走来的得与失。
COLA 作为一种架构思想无疑是成功的。但是作为框架个人感觉有点鸡肋之嫌。特别是在简洁性上做的不好感觉做了不少画蛇添足的事情。
试想一下有些功能我作为作者都很少去使用我实在想不到它为什么还有存在的理由。
基于上面的思考我做了这一次 COLA 2.0 到 COLA 3.0 的升级。在本次升级中我没有增加任何新的功能而是尽量多删减了一些概念和功能。让 COLA 可以更加纯粹的 focus 在应用架构上而不是框架支持和架构约束上。
支持我做这些决策的背后原因只有一个——奥卡姆剃刀原理。
奥卡姆剃刀原理
奥卡姆剃刀原理是指如无必要勿增实体Entities should not be multiplied unnecessarily即“简单有效原理”。正如奥卡姆在《箴言书注》2 卷 15 题说“切勿浪费较多东西去做用较少的东西同样可以做好的事情。”
在具体的应用过程中我们可以遵循以下原则去做事情
“如果同一个现象有 n 种理论最简单的那个便是最正确的。能用 n 做好事情那就不要有第 n1 个动作。”
比如《皇帝的新衣》的皇帝到底穿没穿衣服呢如果你在现场你很有可能就是大臣之一。
如果懂得了奥卡姆剃刀原理可以用逻辑手段判断谁是真理。
第一种逻辑如下假设皇帝是真的穿了衣服→假设愚蠢的人看不见→假设你就是愚蠢的人→所以你没看见皇帝穿衣服第二种逻辑如下假设皇帝没穿衣服→所以你没看见皇帝穿衣服。
同样看见光身子的皇帝第二种解释简单明了。而第一种解释可能正因为它是错误的就需要更多假设来补救漏洞就像说谎圆谎一样。
真相不需要伪装掩饰简单明了。
再比如地心说和日心说托勒密的地心说模型是一个本轮均轮模型。人们可以按照这个模型定量计算行星的运动据此推测行星所在的位置。
到了中世纪后期随着观察仪器的不断改进人们能够更加精确地测量出行星的位置和运动观测到行星实际位置与这个模型的计算结果存在偏差一开始还能勉强应付后来小本轮增加到八十多个却仍然不能精确地计算出行星的准确位置。
1543 年波兰天文学家哥白尼在临终时发表了一部具有历史意义的著作——《天体运行论》。这个理论体系提出了一个明确的观点太阳是宇宙的中心一切行星都在围绕太阳旋转。该理论认为地球也是行星之一它一方面像陀螺一样自转一方面又和其他行星一样围绕太阳转动。 哥白尼的计算不仅结构严谨而且计算简单与已经加到八十余个圈的地心说相比哥白尼的计算与实际观测资料能更好地吻合。因此地心说最终被日心说所取代。
设计中的弯弯绕
深入考察一下我们系统中类似于“地心说”这样的弯弯绕的设计实在是不在少数。
从系统架构的角度看有些弯弯绕是因为系统边界划分不合理导致职责不清依赖混乱。
从应用架构的角度看有些弯弯绕是因为过度设计为了追求所谓的灵活性和可扩展性使用了不恰当的设计。导致本来可以直观呈现的代码逻辑被各种包装各种隐藏各种转发.... 无形中极大的阻碍了代码的可读性和可理解性增加了维护成本。
举个例子我看过无数的业务系统喜欢拿业务流程编排说事情。因此在业务系统中可以看到各种五花八门的“弯弯绕设计”。
比如在一个业务系统中我看到了如下的 pipeline 设计。这个设计的本质是说把一个复杂的业务操作进行结构化拆解为多个小的处理单元。 拆解是正确的但是这种处理方式显然不够“奥卡姆”关于更多结构化分解的内容可以看我的另一篇文章如何写复杂业务代码。作为维护人员进入“入口函数”后还要去查数据库然后才能知道哪些组件被调用了太绕了不够直观也不简洁。
同样的逻辑按照下面的方式写不香吗
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 等架构思想的一种应用架构。 在这次升级中架构思想部分基本没有变化唯一一点是因为去除了 Command 概念因此 CQRS 也成了可选项而不再是一种强要求。
另一方面 COLA 也是框架组件通过这次升级我使用奥卡姆剃刀砍掉了绝大部分的组件能力仅仅保留了扩展点功能。其用意是不希望 COLA 作为框架给到应用开发者太多的约束这不符合简单有效的风格。
所以总结下来与其说这是一次升级不如说它是功能“降级”是在做减法。
但我相信减法可以让 COLA 更加符合奥卡姆精神帮助 COLA 轻装上阵走的更远。
COLA 开源地址https://github.com/alibaba/COLA
阿里云 JAVA 应用脚手架
start.aliyun.com 是基于 Spring-initializr 实现的工程脚手架生成平台开发者们只需要添加一些注解和少量配置就可以快速搭建分布式应用系统它使用更亲切的中文也不会有网络延迟问题最重要的是提供更多本地化的组件依赖。
点击链接立即体验阿里云 JAVA 应用脚手架https://start.aliyun.com/?utm_contentg_1000150531 “阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践做最懂云原生开发者的公众号。” 原文链接 本文为云栖社区原创内容未经允许不得转载。