一文读懂架构整洁之道
数据层:负责与 DB 等底层数据存储介质通信,通常包括 Mapper 和 DO 等;
? ?垂直分层
但是,仅仅做到水平分层是不够的。当业务逻辑比较复杂时,涉及的用例会非常多,这些用例之间的变更原因几乎肯定是不同的,所以还要进行垂直分层,将变更原因不一样的用例切分开。
例如,很多系统的修改操作和删除操作的变更原因和变更频率是不一样的,这时候可以考虑将修改和删除进行解耦。
不论是水平分层还是垂直分层,其核心目的都是将更新频率不同的代码给分开,放入不同的组件中。
? ?解耦方式
分层后的解耦方式有多种:
源码层次:通过控制源代码模块间的依赖关系进行解耦,部署时仍然一起部署,适用于项目早期刚起步时;
部署层次:通过控制部署单元(例如 jar 包等)之间依赖进行解耦,当系统对部署和开发方面有更高的要求时,部分组件需要独立出去形成新的部署单元;
服务层次:通过将组件的依赖关系降低到数据结构级别,然后通过服务进行通信来解耦,当系统足够大的时候,就需要服务层次的解耦了;
但需要注意的是,不论通过哪种解耦方式进行代码的隔离,并不意味着这样就万事大吉,拥有良好可扩展和可维护性了。这也是 Bob 在《架构整洁之道》中提到的“横跨型变更”(虽然作者是在服务的这一章提出的,但我认为也适用于其他两种解耦层次)。
请看下面的出租车调度系统的服务架构图
图中可以看出,Taxi UI 依赖 Taxi Finder 查找符合条件的出租车,依赖 Taxi Selector 进行出租车调度。而 Taxi Finder 通过多个 Taxi Supplier 服务获取车辆信息,Taxi Selector 依赖 Taxi Dispatcher 进行最终的派单。
各个组件都是服务化的。可以看到,各个组件都是具体的类,虽然各个组件隔离部署,但其实他们之间是强耦合的,并没有真正的解耦:加入现在出租车公司准备推出运送猫咪的服务,则所有的组件都需要进行更改,同时有些同学的更改方式就是在原有的类中增加 if...else,这显然是不可取的。
正确的做法应该是在组件最初的设计中,就应该考虑抽象化和多态,如下图,使用策略模式或者模板方法进行解耦:
注意看我红框标出来的,除了服务间的隔离外,在组件内部其实也存在隔离,而这个的隔离更加重要。这也就是书中讲的:
服务边界并不能代表系统的架构边界,服务内部的组件边界才是。
架构
从代码和组件原则到组件分层和解耦,我们逐渐对系统底层的一些元素有了比较深入的了解,那么上层的架构到底是什么呢?
? ?什么是架构
并没有非常明确的定义,这里引用书中的一些描述,大家应该有一些体会:
软件架构的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式。
软件架构的终极目标是,用最小的人力成本满足构建和维护该系统的需求
需要指出的是,架构和框架并不是相同的东西:
架构一定是业务相关的,包含了业务属性,并且这个业务属性是系统的核心价值;
框架一般都是业务无关的,是我们编码实现架构的的工具,属于实现细节。
最初设计系统架构时,并不需要过多考虑使用什么框架,而更多的是关注自身业务。
此外,很多人可能对架构有些误解:设计那么多有什么用,代码不还照样得写?
是的,代码还得写,架构并不能让你不写代码了(有时可能还会让你多写代码)。但是,好的架构会让写代码变得更容易了。
容易不一定是体现在需要变更的代码量多少上,好的架构可以让你更快速的找出需要变更的范围,并且很容易的就修改掉——这对于一个运行维护了多年的系统尤为重要。大家可以回想下这样的场景是不是很熟悉:明明是一个看起来很正常很合理的需求,看起来变更范围也不大,但真的去撸代码时发现需要改的地方好多,甚至无从下手去改。这个时候可能就要去看看之前的架构设计是不是不够合理,有哪些需要优化改进的。
六边形架构
六边形架构,又名端口适配器架构(我更喜欢这个名字,因为六边形老让人感觉有六个什么东西跟它对应),DDD 极力推崇该架构。系统的领域模型是系统最为重要的部分,而其他的诸如 DB,UI,缓存,消息队列等等均通过适配器与领域层进行通信,也就是依赖关系是由外到内的(依赖倒 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 置)。之前在商家规模化运营项目中也尝试过使用 DDD 来进行架构设计。
? ?整洁架构
Bob 综合六边形架构和其他几个架构的特点提出了整洁架构,它具有以下几个特点:
独立于框架
可被测试
独立于 UI
独立于数据库
独立于任何外部机构
架构最内部是业务实体,代表了系统关键业务逻辑。
再外一层是用例——特定应用场景下的业务逻辑,在 Bob 看来,用例是架构设计中非常重要的一环,架构也是基于这些用例进行设计的,如果缺失了用例,就无从谈起架构设计了。
紧接着是控制器,网关和展示器,是一层接口适配器。
最外层则是框架和驱动程序,这里面包含了数据库,web,工具等等,这一层一般只包含了一些通信的黏合性代码。
也并不一定只有四层,真实的情况可能会超过四层,但总的原则不变:外层依赖内存,最内部是最通用、最高层的策略,最外层是最具体的实现细节。
外层的是底层组件,内层的是高层组件,底层组件作为高层的插件存在,换句话说外层的这个组件是可以被其他的组件像插件一样替换掉的。
这里需要明确的一点是:有时候,软件运行的方向与依赖的方向是不同的(这个很多同学可能会没有注意到)。比如业务实体从数据库取数据,运行方向是业务实体->数据库,而通过依赖反转(DI,业务实体定义查询接口,数据库层实现),我们的依赖关系是业务实体<-数据库。这里讲到的保持底层组件对高层组件的依赖,高层组件的稳定性和抽象性,也就是前面讲的 SDP 和 SAP。
总结
本文从代码和组件的原则讲起,讲到组件内和组件间的关系,以及如何进行组件的分层和隔离,接着引出了架构相关的讨论,列举了六边形架构和整洁架构,并谈了一些自己的理解。
后续会结合自身做过的项目,谈一谈具体的一些架构模式。
参考:
《架构整洁之道》 Robert C. Martin
《领域驱动设计》 Eric Evans
《企业应用架构模式》Martin Fowler
文末福利
知识图谱获取
星标或置顶「淘系技术」微信公众号,截图发送后台。
文章作者
阿里巴巴 淘系技术 开发工程师——马飞翔(花名:泽畔)
图谱来源
阿里巴巴 ICBU 技术部 高级无线开发工程师——韩帅
???拓展阅读
评论