05 项目诊断与改进:如何进行组件化分析和设计?
你好,我是黄俊彬。上节课我们一起学习了四种移动应用架构的演进,也给你介绍了不同架构演进过程中遇到的各种问题。相信你已经感受到,遗留系统不仅会带来研发效率和质量方面的问题,还会影响产品的市场响应力。
今天我们正式进入到这个课程的示例项目中,看看Sharing旧架构(1.0 时期)的代码结构和工程管理方式,对它做一次诊断和优化。通过这节课的学习,你可以了解遗留系统常见的代码、工程组织方式及其存在的问题,并掌握组件化的分析和设计思路。
为了降低你对业务上下文以及代码的理解成本,课程的Sharing项目采用的是浓缩版的示例,其中的数据采用的都是本地数据,业务功能也做了精简。但该项目包含了遗留系统各类典型代码坏味道以及代码耦合问题,你可以将这个课程中学习的流程方法、工具、设计思想无缝运用到其他的项目中。
Sharing 1.0:案例诊断
我们来对Sharing做个案例诊断。
代码结构
Sharing 1.0 采用的是单体的架构,所有的的代码都在一个模块中,主要包含了账户、文件和消息3个模块。账户模块主要管理用户个人信息、登录及登出;文件模块主要负责用户上传文件及浏览文件;消息模块主要负责用户共享文件给所有用户。
另外,Sharing代码的组织方式是按技术维度来划分,并不是以业务维度进行划分,这是单体架构常见的代码组织方式。Sharing主要的包结构和功能我梳理了一张表。
按技术维度组织代码最大的问题就是:如果缺少架构约束,容易导致代码随意依赖,耦合度高。
所以。我的建议是代码的组织方式最好以业务维度来组织,这样代码可以更加内聚,架构依赖的检查规则也更容易编写。例如业务模块之间的横向依赖,消息主页面依赖了文件模块及账户模块的代码,像后面图里展示的这样。
再比如一些基础的组件直接依赖业务模块的代码。
总的来说,Sharing目前的代码结构主要是以技术维度来组织代码,但由于缺少架构约束,内部的代码耦合度高。
工程管理
Sharing目前采用的是单个Git仓管理的方式,但由于前面的业务划分不清晰,导致很多时候多个开发同学修改到同一处代码,这样在代码提交时非常容易产生冲突。另外,团队也没有统一的分支策略,通常有多个并行的开发分支和线上分支需要维护。你可以结合下图理解。
可以看出,团队缺少统一的分支模型,开发人员随意拉取分支,各个特性的代码不能及时同步,没有一个分支拥有代码的全集。随着时间的推移,分支上的代码差异越来越大,大家也不敢随意合并分支代码,只能通过cherry pick的方式来同步一小部分代码。另外,很多线上问题也经常是由于那部分代码没有同步导致的。
在这么无序的分支模型下,我们来看看团队是怎么来管理制品的生成。团队使用了Jenkins搭建了一个简单的流水线,主要是为了方便测试人员触发打包进行手工验证。我同样画了一张图帮你理解。
从图看出,测试人员在测试时会通过Jenkins平台手动选择需要打包的分支,然后点击进行构建,构建完成后下载安装包到本地安装。从作用上来看,流水线变成了一个“打包平台”,仅服务于测试人员,对开发人员无感。
针对Sharing项目的这种情况,我们怎么改进呢?
建议在架构上,通过组件化让各个模块的代码边界更加清晰,减少代码的随意依赖。同时,这也能让各个组件的职责更加单一、明确,便于代码扩展。在工程上,我们可以结合组件化架构搭建分层、分级的流水线,增加门禁守护以及版本自动化构建,统一规范,减少人工参与版本的发布。
结合上面的分析,我们总结一下Sharing项目当前的问题和改进建议。你可以看看后面这张项目诊断表。
Sharing 2.0:改进设计
对Sharing的优化主要分为两个部分:架构和工程实践。我们会优先对架构进行组件化,而工程实践部分我会在“持续交付篇”中详细介绍。
组件化架构的设计有两个重要的核心工作,第一个是划分组件,第二个是把这些组件组织成一个系统,我们一起来看看。
组件划分
在维基百科中提到,基于组件的开发(Component-Based Development,简称CBD)是针对系统的广泛功能,进行关注点分离的软件工程方式。此方式是以复用为基础的作法,定义、实现许多松耦合的独立组件(Component),再将组件组合成系统。
这其中有几个关键词已经说明了组件化的好处,包括关注点分离、松耦合和复用。但是对于团队来说,做组件化设计时一定会遇到一个问题,就是如何划分组件?
我们先来看看在移动应用中有哪些常见的组件,基于之前的经验,我将组件划分为3种类型,你可以参考我梳理的表格。
通常来说,在项目代码中,技术组件和功能组件比较好识别,因为它们有一个共同的特点:被多个业务使用,组件之间的边界彼此也相对清晰。所以,业务组件的划分可以参考UI设计。
一般情况下,产品在设计时都会将相对内聚的功能组织在一个页面上,便于用户使用。这点也是移动应用在组件划分上相较于后端微服务划分的一个优势。
下面我们按照业务、功能和技术这3种组件类型,对Sharing项目进行一次组件的全景梳理。
我们先来划分业务组件。根据页面的设计,产品划分了三个功能模块,分别为消息、文件和账户。
由此,我们可以划分出三大业务组件:消息组件、文件组件和账户组件,然后将相关联的包或类归属到对应的组件下。
注意,在梳理哪些类属于这个组件时,建议根据用户的使用路径,从一开始的操作入口的页面类开始,这样最简单。然后接着查看一下这个类的依赖和引用情况,如果该类频繁与某个业务组件内的类有交互,那么这个类大概率也要划分在对应的组件内。
在实际项目中,一个组件内的类可能成百上千,这个时候我们可以先从包的维度来分析。根据前面的分析,Sharing的三大业务组件最后划分如下:
接着,我们来梳理功能组件。在梳理功能组件时,我同样建议先查看被前面业务组件多次引用的类,如果一个类被多个业务组件所引用,那么大概率这个类要被划分到功能组件的范围。比如Sharing中的文件上传下载功能,这个功能会同时被消息和文件模块使用,对此,我们可以考虑将其作为独立的功能组件。
最后,我们来梳理一下技术组件,我们依旧通过查看引用的方式来判断。功能组件和技术组件经常被混淆,其实区分技术组件与功能组件的依据是:技术组件能放在任何一个应用里都能使用,与具体的业务不产生关系,而功能组件不一定都行。
组件组合
划分好组件以后,就可以进行架构设计了。架构的设计重点就是将这些组件组合成系统,明确这些组件之间的依赖关系和约束条件。组件组合的关键是管理好组件之间的依赖,从依赖方向上来看,这三类组件的依赖关系应该是:业务组件->功能组件->技术组件。根据这个依赖方向,我们再抽象一个分层的概念,将各类组件划分到不同的分层中,这样边界职责就更加清晰了。
基于这个分析,我们重新设计Sharing 2.0的组件化架构。
Shariing 2.0 新的架构设计主要包含三个横向的分层,依次对应着三类组件。
- 第一层是业务层,主要承载的是各类独立演进的组件。
- 第二层是框架层,主要承载的是各类复用的功能组件。
- 第三层是基础组件层,主要承载的是各类公用的技术组件。
特别需要注意的是,在架构设计中有一个基座的功能组件,该组件主要作为各个业务组件的底座,能灵活插拔各个业务组件。
根据新的分层架构设计,Sharing 2.0架构有两个重要的约束原则:
- 纵向规则:上层组件可以依赖下层组件,下层组件不能反向依赖上层的组件。
- 横向规则:业务组件之间不能有直接的依赖,功能及技术组件之间尽量减少依赖。
总结
今天这节课到这里就结束了,通过对Sharing项目的诊断与设计改进,我给你介绍了遗留系统常见的架构和工程问题,并讲解了如何进行组件化架构设计。
遗留系统常见的代码组织方式是按技术维度来组织,如果缺少规范和架构约束,非常容易出现代码随意依赖、耦合度高的问题。所以,我们应尽可能以业务的维度来组织代码,这样更易于维护。而遗留系统在工程上的问题往往也是缺少规范和过程约束,更容易加剧代码的腐化。
针对Sharing 1.0的架构问题,我们进行了组件划分和架构设计。我们将组件划分为业务组件、功能组件和技术组件,其中,业务组件主要是承载业务,可复用性低;功能组件通常是业务组件的一部分,被各个业务组件复用;技术组件主要是支撑业务的开发,与业务不关联,能在多个应用之间进行复用。
结合组件的划分,我们设计了新的三层架构来组织这三类组件,分别是业务层、框架层和基础组件层。而这种架构思想就是:业务独立演进,公共能力复用。
到这里,我们分析了Sharing旧的架构痛点,同时也规划了新的架构。那么问题来了,怎么落地这个规划呢?下节课,我们将认识一些遗留系统常见的架构分析工具,梳理一下将当前架构重构为未来架构需要完成哪些工作。
思考题
感谢你学完了今天的内容,今天的思考题是这样的:你觉得你现在的项目在代码架构和工程管理上有什么问题吗?
欢迎你在留言区与我交流讨论,也欢迎你把这节课分享给你的同事或朋友,我们一起来高效、高质量交付软件!
- peter 👍(3) 💬(1)
请教老师几个问题: Q1:分层时,每层只能是同一类的组件吧。 比如业务层,该层只能是业务组件,不能包含功能组件和技术组件,对吧。 Q2:Sharing这个项目有源码吗? Q3:按技术维度划分,如果有架构约束和规范,也可以采用,是吗? 文中有“遗留系统常见的代码组织方式是按技术维度来组织,如果缺少规范和架构约束,非常容易出现代码随意依赖、耦合度高的问题”,从这句话看,如果按技术维度划分,也可以,但前提是要有比较好的架构约束和规范,对吗? Q4:在具体操作层面,在AS下为每个组件创建项目或module,然后打包成aar或so,在最终的集成项目中引用这些aar或so,是这样吗? Q5:集成方式中有一种apk,一个项目可以引用一个apk吗?(不清楚此时是如何具体操作的)
2023-02-20 - 开飞机的老舒克 👍(0) 💬(1)
请教老师个问题,就是如果登录、用户信息这些都封装在业务层的用户组件中,但是其他业务组件想使用这里面的功能或者信息的时候是需要在功能组件层提供路由支持吗?例如我在消息组件中需要更具登录态做一些操作之类的功能时,需要和用户组件通信,请问有什么好的通信方式吗?
2023-02-28 - 迷途的羔羊 👍(0) 💬(1)
首页有4个tab,个人中心,和三个其它类型界面,按照这4个界面分4个业务组件可以吗?其它界面按照业务属性归到这四个里面
2023-02-23 - Justin 👍(0) 💬(1)
怪不得大公司都要分的很细,做好一个大型app不容易
2023-02-21