淘系技术 马飞翔(泽畔)

读完需要

9

分钟

速读仅需 3 分钟

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

1

原则

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

1.1

代码原则

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

1.2

组件原则

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

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

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

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

  • CCP:共同闭包原则

  • CRP:共同复用原则

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

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

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

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

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

这里贴下书中的张力图:

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

1.3

组件耦合

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

  • ADP:无依赖环原则

  • SDP:稳定依赖原则

  • SAP:稳定抽象原则

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

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

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

2

分层与解耦

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

2.1

水平分层

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

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

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

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

2.2

垂直分层

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

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

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

2.3

解耦方式

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

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

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

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

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

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

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

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

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

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

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

3

架构

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

3.1

什么是架构

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

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

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

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

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

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

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

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

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

4

六边形架构

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

六边形架构

5

整洁架构

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

  • 独立于框架

  • 可被测试

  • 独立于 UI

  • 独立于数据库

  • 独立于任何外部机构

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

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

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

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

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

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

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

6

总结

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

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

参考:

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

  • 《领域驱动设计》 Eric Evans

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

文章作者

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

图谱来源

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

- EOF -

想要加入中生代架构群的小伙伴,请添加群合伙人大白的微信

申请备注(姓名+公司+技术方向)才能通过哦!

扩展阅读

DDD专家张逸:构建领域驱动设计知识体系 2020-10-13

京东架构师闫文广:订单系统高可用架构及演变过程 2020-10-12

架构师,是否需要写代码? 2020-09-18

阿里高级技术专家箫逸:如何画好一张架构图? 2020-09-07

大神手把手教你设计秒杀架构模型 2020-09-06

阿里巴巴闲鱼架构负责人王树彬:万亿交易规模技术架构实践 2020-09-05

阿里高级技术专家张建飞:应用架构分离业务逻辑和技术细节之道 2020-08-31

波波老师大解密:如何成为优秀的架构师? 2020-06-25

微信支付软件架构重构之旅 2020-06-08

一个思维习惯,让你成为架构师 2020-06-03

阿里P9专家右军:以终为始的架构设计 2020-04-27

   END
#架构师必备#

点分享点点赞点在看

阿里专家马飞翔:一文读懂架构整洁之道相关推荐

  1. 一文读懂架构整洁之道(附知识脉络图)

    程序的世界飞速发展,今天所掌握的技能可能明年就过时了,但有一些东西是历久弥新,永远不变的,掌握了这些,在程序的海洋里就不会迷路,架构思想就是这样一种东西. 本文是<架构整洁之道>的读书笔记 ...

  2. 再读《架构整洁之道》

    时至今日,软件开发技术中最热闹的领域就是前端开发了,各种 xxxScript 语言,各种前端框架,以至于很长时间都没有再听过"面向对象"这种"古老"的词汇了.作 ...

  3. 十大报表工具 BI 产品深度点评-----BI报表行业专家干货分享----一文读懂报表与BI的行业现状----最新常用BI报表工具对比科普帖

    目前国内市场上的报表 BI 工具琳琅满目,看起来也各有特点,这给选型工作带来了一些困扰,本文就一些较活跃的报表 BI 产品进行点评,对于不太熟悉这些产品和技术的同学,可作为参考资料. 这里选了十个产品 ...

  4. 一文读懂架构师都不知道的isinstance检查机制

    起步 通过内建方法 isinstance(object, classinfo) 可以判断一个对象是否是某个类的实例.但你是否想过关于鸭子协议的对象是如何进行判断的呢? 比如 list 类的父类是继 o ...

  5. 一文读懂kafka(附加52道常见面试题)

    1.Kafka基本概念 1.什么是kafka Kafka是一个分布式消息中间件,支持分区,多副本,多订阅者,基于zookeeper协调的消息系统,它提供了类似于JMS的特性,但是在设计实现上完全不同, ...

  6. 一文读懂NLP之隐马尔科夫模型(HMM)详解加python实现

    一文读懂NLP之隐马尔科夫模型(HMM)详解加python实现 1 隐马尔科夫模型 1.1 HMM解决的问题 1.2 HMM模型的定义 1.2.1HMM的两个假设 1.2.2 HMM模型 1.3 HM ...

  7. 一文读懂阿里云网络 2020 云栖大会新品发布

    凌云时刻 · 极鲜速递 导读:阿里云网络新品来袭! 来源 | 洛神云网络技术 前言 企业上云,网络先行.在 2020 云栖大会上,阿里云基础产品事业部网络产品团队负责人祝顺民宣布了云网络一系列新品发布 ...

  8. 一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现

    一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现 导读:近日,马云.马化腾.李彦宏等互联网大佬纷纷亮相2018世界人工智能大会,并登台演讲.关于人工智能的现状与未来,他们提出了各自的观点,也引 ...

  9. 你真的懂数据分析吗?一文读懂数据分析的流程、基本方法和实践

    导读:无论你的工作内容是什么,掌握一定的数据分析能力,都可以帮你更好的认识世界,更好的提升工作效率.数据分析除了包含传统意义上的统计分析之外,也包含寻找有效特征.进行机器学习建模的过程,以及探索数据价 ...

最新文章

  1. android ListView中长按背景颜色可见性设置
  2. 在ueditor编辑器的光标停留处插入内容
  3. 【数理知识】《数值分析》李庆扬老师-目录
  4. MySQL 当记录不存在时insert,当记录存在时update
  5. mysql创建一个表用来快速查询表_mysql数据库的创建表格、查询(多表查询)
  6. C中的malloc:C中的动态内存分配
  7. 分别采用深度优先遍历和广度优先遍历判断是否存在由vi到vj的路径,图用邻接表存储
  8. 剑指offer——面试题59:对称的二叉树
  9. Ubuntu下安装Adobe Reader的中文语言包
  10. flAbsPath on /var/lib/dpkg/status failed 解决 Cydia 红字
  11. php-elasticsearch 单条、批量插入数据
  12. LINUX模拟键盘F5的脚本(未试过)
  13. bgm去水印解决办法 PR模板音乐素材去水印的方法
  14. Mac安装tree命令
  15. Reincarnation HDU - 4622 (后缀自动机)
  16. 以太坊的单位wei是什么?
  17. 世界上第一天微型计算机,day 1:计算机发展史和组成部分
  18. 想在微信上使用chatGPT?小程序?公众号?企业微信,最终还是选择了企业微信版本的chatgpt
  19. 用php完成下拉菜单,最新的8个实现下拉菜单功能的总结
  20. getRemoteAddr和getLocalAddr的区别

热门文章

  1. 找回django admin密码
  2. 达人评测 i7 1355U和i5 1340p选哪个 i71355U和i51340p对比
  3. 微擎 用户是否已关注公众号
  4. 弘辽科技:拼多多怎么会判定为提升销量?会有什么处罚?
  5. 「镁客·请讲」酷哇机器人刘力源:主打低速自动驾驶场景,AI环卫需求正在急速增长... 1
  6. 拖拽获取文件路径html,Lovestu - electron 原生拖拽文件获取路径
  7. apdu 移动sim_中国移动手机支付业务及关键技术
  8. 【Apache OFBiz 系列】OFBiz的数据结构分析(五)
  9. Bootstrap笔记(十) 图片篇
  10. kali根目录磁盘扩容(已解决问题no space left on device)