15

一文读懂架构整洁之道

 3 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzAxNDEwNjk5OQ%3D%3D&%3Bmid=2650408679&%3Bidx=1&%3Bsn=dd8c6099af3a1529bff4ea46631d9e4b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MVb2ai6.gif

相信大家都非常清楚,如何编写可读性强的代码是一个合格程序员的必修课。

我在之前的文章 谈谈什么是好的代码》 中谈了一些自己对整洁代码的感悟,代码并不是独立存在的,成百上千个类的系统在企业应用中非常常见,如何将代码进行有效的组织,保持高可读性,高可维护性,则是一个好的架构需要考虑的事情。本文从原则切入,聊聊组件的分层和解耦,浅谈下Bob大叔提出的整洁架构,感兴趣的同学也可以发表下自己的看法。

原则

原则属于做事情的指导方针,在讨论架构前,先来看看相关的一些原则。有适用于代码层面的原则,有适用于再高一层级——组件的原则。

 代码原则

代码的原则有SOLID,迪米特法则,组合复用原则等等,我在 谈谈什么是好的代码 中也列出来过,关于这些原则讨论的文章非常之多,大家也比较熟悉,本文主要谈架构方面的,这里就不展开了。

 组件原则

组件是一组代码的集合,拿盖房子来打比方,代码原则指导如何使用砖块建造房间,而组件原则指导如何将房间构建成高楼大厦。组件的构建要遵循一些原则,否则即使墙砌的再好,房间修建的再漂亮,不按照规范建造,房子可能就歪歪扭扭,整栋楼房的质量也堪忧。

组件原则包括组件内的关系(组件聚合)以及组件间的关系(组件耦合)。

组件聚合

组件聚合方面的原则有以下几个:

  • REP:复用/发布等同原则

  • CCP:共同闭包原则

  • CRP:共同复用原则

REP是组件聚合总的指导原则,表示组件内的类和模块是彼此紧密相关的。CCP和CRP是对REP的补充,CCP可以看做是组件级别的单一职责原则 (SRP): 由于相同原因修改,并且需要同时修改的东西放一起; 不同原因修改,并且不同时修改的东西分开。 CRP可以看做是组件级别的接口隔离原则(ISP): 不要依赖不需要的东西。

可以看出通过这三个原则构建的组件,拥有以下几个特点:

  • 组件内的类和模块紧密相关,需求变更时通常需要同时修改;

  • 需求变更时,需要进行的修改只涉及很少的组件甚至在一个组件内;

  • 复用这个组件时,通常组件内的功能均是用户需要的,而不是有一些不相关的功能;

这里贴下书中的张力图:

3e6rAvN.png!web

项目的初期,更多关注的是维护性而牺牲复用性,随着项目逐渐成熟,项目重心会逐渐倾向于复用性。

组件耦合

组件耦合方面的原则有以下几个:

  • ADP:无依赖环原则

  • SDP:稳定依赖原则

  • SAP:稳定抽象原则

组件间的依赖如果存在环,则维护性将大大降低,我们应该避免组件间的循环依赖。


组件间的依赖关系应该是指向更稳定的方向,每个组件的稳定性都低于其依赖的组件稳定性。组件越稳定,则其抽象程度需要更高;组件越不稳定,则其抽象程度需要更低,越具体。

有时候,可能会新引入一个引用,导致一个稳定的组件依赖了一个不稳定的组件,此时就可以使用依赖倒置原则(DIP)将依赖关系反转,保持稳定依赖。

分层与解耦

谈了组件相关的原则,现在来谈谈组件间的分层和隔离的方式。通过有效的分层手段,可以有效隔离不同功能的组件。

  水平分层

得益于MVC模式的普及,水平分层在我们的系统中已经非常普及了,通常有以下几层:

  • 表现层/UI层:负责系统的界面展示,通常包括Controller和VO等;

  • 领域层/业务逻辑层:负责处理系统的业务逻辑,通常包括Service,Manager和DTO等;

  • 数据层:负责与DB等底层数据存储介质通信,通常包括Mapper和DO等;

  垂直分层

但是,仅仅做到水平分层是不够的。 当业务逻辑比较复杂时,涉及的用例会非常多,这些用例之间的变更原因几乎肯定是不同的,所以还要进行垂直分层,将变更原因不一样的用例切分开。

例如,很多系统的修改操作和删除操作的变更原因和变更频率是不一样的,这时候可以考虑将修改和删除进行解耦。

不论是水平分层还是垂直分层,其核心目的都是将更新频率不同的代码给分开,放入不同的组件中。

 解耦方式

分层后的解耦方式有多种:

  • 源码层次:通过控制源代码模块间的依赖关系进行解耦,部署时仍然一起部署,适用于项目早期刚起步时;

  • 部署层次:通过控制部署单元(例如jar包等)之间依赖进行解耦,当系统对部署和开发方面有更高的要求时,部分组件需要独立出去形成新的部署单元;

  • 服务层次:通过将组件的依赖关系降低到数据结构级别,然后通过服务进行通信来解耦,当系统足够大的时候,就需要服务层次的解耦了;

但需要注意的是,不论通过哪种解耦方式进行代码的隔离,并不意味着这样就万事大吉,拥有良好可扩展和可维护性了。这也是Bob在《架构整洁之道》中提到的“横跨型变更”(虽然作者是在服务的这一章提出的,但我认为也适用于其他两种解耦层次)。

请看下面的出租车调度系统的服务架构图

yymENbm.png!web

图中可以看出,Taxi UI依赖 Taxi Finder查找符合条件的出租车,依赖Taxi Selector进行出租车调度。而Taxi Finder通过多个Taxi Supplier服务获取车辆信息,Taxi Selector依赖Taxi Dispatcher进行最终的派单。

各个组件都是服务化的。可以看到,各个组件都是具体的类,虽然各个组件隔离部署,但其实他们之间是强耦合的,并没有真正的解耦:加入现在出租车公司准备推出运送猫咪的服务,则所有的组件都需要进行更改,同时有些同学的更改方式就是在原有的类中增加if...else,这显然是不可取的。

正确的做法应该是在组件最初的设计中,就应该考虑抽象化和多态,如下图,使用策略模式或者模板方法进行解耦:

j6ZbErZ.png!web

注意看我红框标出来的,除了服务间的隔离外,在组件内部其实也存在隔离,而这个的隔离更加重要。这也就是书中讲的:

服务边界并不能代表系统的架构边界,服务内部的组件边界才是。

架构

从代码和组件原则到组件分层和解耦,我们逐渐对系统底层的一些元素有了比较深入的了解,那么上层的架构到底是什么呢?

  什么是架构

并没有非常明确的定义,这里引用书中的一些描述,大家应该有一些体会:

软件架构的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式。
软件架构的终极目标是,用最小的人力成本满足构建和维护该系统的需求

需要指出的是,架构和框架并不是相同的东西:

  • 架构一定是业务相关的,包含了业务属性,并且这个业务属性是系统的核心价值;

  • 框架一般都是业务无关的,是我们编码实现架构的的工具,属于实现细节。

最初设计系统架构时,并不需要过多考虑使用什么框架,而更多的是关注自身业务。

此外,很多人可能对架构有些误解:设计那么多有什么用,代码不还照样得写?

是的,代码还得写,架构并不能让你不写代码了(有时可能还会让你多写代码)。但是,好的架构会让写代码变得更容易了。

容易不一定是体现在需要变更的代码量多少上,好的架构可以让你更快速的找出需要变更的范围,并且很容易的就修改掉——这对于一个运行维护了多年的系统尤为重要。大家可以回想下这样的场景是不是很熟悉:明明是一个看起来很正常很合理的需求,看起来变更范围也不大,但真的去撸代码时发现需要改的地方好多,甚至无从下手去改。这个时候可能就要去看看之前的架构设计是不是不够合理,有哪些需要优化改进的。

六边形架构

六边形架构,又名端口适配器架构(我更喜欢这个名字,因为六边形老让人感觉有六个什么东西跟它对应),DDD极力推崇该架构。系统的领域模型是系统最为重要的部分,而其他的诸如DB,UI,缓存,消息队列等等均通过适配器与领域层进行通信,也就是依赖关系是由外到内的(依赖倒置)。之前在商家规模化运营项目中也尝试过 使用DDD来进行架构设计

nMfA3mu.png!web

  整洁架构

Bob综合六边形架构和其他几个架构的特点提出了整洁架构,它具有以下几个特点:

  • 独立于框架

  • 可被测试

  • 独立于UI

  • 独立于数据库

  • 独立于任何外部机构

fIFfueF.png!web

架构最内部是业务实体,代表了系统关键业务逻辑。

再外一层是用例——特定应用场景下的业务逻辑,在Bob看来,用例是架构设计中非常重要的一环,架构也是基于这些用例进行设计的,如果缺失了用例,就无从谈起架构设计了。

紧接着是控制器,网关和展示器,是一层接口适配器。

最外层则是框架和驱动程序,这里面包含了数据库,web,工具等等,这一层一般只包含了一些通信的黏合性代码。

也并不一定只有四层,真实的情况可能会超过四层,但总的原则不变:外层依赖内存,最内部是最通用、最高层的策略,最外层是最具体的实现细节。

外层的是底层组件,内层的是高层组件,底层组件作为高层的插件存在,换句话说外层的这个组件是可以被其他的组件像插件一样替换掉的。

这里需要明确的一点是:有时候,软件运行的方向与依赖的方向是不同的(这个很多同学可能会没有注意到)。比如业务实体从数据库取数据,运行方向是业务实体->数据库,而通过依赖反转(DI,业务实体定义查询接口,数据库层实现),我们的依赖关系是业务实体<-数据库。这里讲到的保持底层组件对高层组件的依赖,高层组件的稳定性和抽象性,也就是前面讲的SDP和SAP。

总结

本文从代码和组件的原则讲起,讲到组件内和组件间的关系,以及如何进行组件的分层和隔离,接着引出了架构相关的讨论,列举了六边形架构和整洁架构,并谈了一些自己的理解。

后续会结合自身做过的项目,谈一谈具体的一些架构模式。

参考:

《架构整洁之道》 Robert C. Martin

《领域驱动设计》 Eric Evans

《企业应用架构模式》Martin Fowler

文末福利

知识图谱获取

星标置顶 「淘系技术」微信公众号,截图发送后台。

文章作者

阿里巴巴 淘系技术 开发工程师——马飞翔(花名:泽畔)

图谱来源

阿里巴巴 ICBU技术部 高级无线开发工程师——韩帅

✿  拓展阅读

eaYnmeu.png!web

M7BRzm3.png!web

VNfyIbm.png!web

作者| 马飞翔(泽畔)

编辑| 橙子君

出品| 阿里巴巴新零售淘系技术

veE3IrM.jpg!web

uyQNNnZ.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK