上一讲主要介绍了如何通过代码预检查的方式提高入库代码的质量,将代码检查尽可能前置,降低修复问题的成本,从而提高交付软件的质量。除了代码本身的问题,依赖组件也是经常困扰开发者的一个问题。比如,依赖组件的某个版本存在缺陷或安全漏洞,不再继续使用。不管是第三方的依赖组件,还是企业内部开发的依赖组件,这类问题都是非常普遍的。当发现组件有问题时,你要能知道该组件的基本信息和影响范围,以及采取什么样的措施。今天要介绍的内容——第三方组件管理,就是如何有效管理依赖的第三方组件,使得在节省时间和精力的同时,让系统更可控。这也是我在之前的企业中落地的一个实践。
使用第三方组件管理的原因
在今天的软件开发领域,因为有可复用的第三方组件让软件开发变得越来越简单。**第三方组件就是不是自己写的,可以直接拿来使用的代码。**组件可以是工具类,比如 json 解析的类库;也可以是完整的框架,比如 Spring 框架。这些第三方的组件大多数是开源的,由开源社区维护。也可以是企业购买的付费产品。在软件开发过程中,通过构建管理工具直接添加到项目中使用。使用第三方组件,可以不必“重复造轮子”,节约了时间和精力,加快了软件开发的过程。第三方组件已经是软件开发不可或缺的组成部分。
然而,使用第三方组件并非易事。主要有以下几方面的问题:
& 组件会存在缺陷或安全漏洞;
& 组件的版本会过时;
& 组件需要额外的维护成本。
随着系统的架构越来越复杂,需要依赖的组件也会越来越多,风险和成本也就越来越大。因此,使用第三方组件本质上一个风险和回报的问题,是安全风险问题和增加维护成本与减少开发时间的回报之间的平衡问题。通过对第三方组件的有效管理,可以有效控制第三方组件产生的风险,这也是为什么要做第三方组件管理的主要原因。
第三方组件的管控方式
第三方组件是一个统称,范围很广,涉及任何开发语言。本文的第三方组件主要指 Java 语言中,通过 Maven、Gradle 等构建管理工具添加到应用程序中的依赖组件。为了后面更好的理解第三方组件的管理,这里先介绍一下第三方组件的表示方法和添加方式,进而引出最合适的管控时机。
表示方法
在 Maven、Gradle 等构建管理工具中,第三方组件的命名一般包含三部分。
& groupId:在所有项目中唯一标识,需遵循 Java 的包名命名规则,即以反向域名开头。如 org.springframework.boot。
& artifactId:是不包含版本的模块的名称,如 spring-boot-starter-test。
& version:当前模块的版本,一般为数字和点的形式,如 2.4.0。也会跟组件发布状态有关,如 SNAPSHOT 表示快照版,RELEASE 表示发布版本。
添加方式
下面以 spring-boot-starter-test 依赖组件为例,介绍第三方组件的添加方式:
在 Maven 中, 表示一个第三方组件,将组件的坐标按如下格式添加到项目的 pom.xml 文件 中,即可完成添加。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.4.0</version><scope>test</scope>
</dependency>
在 Gradle 中,可将如下格式的依赖声明添加到 build.gradle 文件中的 dependencies 中,即可完成添加。
testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-test', version: '2.4.0'
管控时机
构建管理工具在构建软件时会经过一系列的阶段,比如:编译、测试、打包,安装、部署等环节。这里有几个关键的环节。
编译:将第三方组件的坐标描述添加到文件中,并不会真正地将组件添加到项目中,而是当触发编译时,构建管理工具才会根据仓库地址的设置进行读取。一般情况下,为了加快读取的速度,会先在本地仓库中查找,如果没有再从远程仓库下载到本地。
测试:基于导入的第三方组件执行的本地单元测试,验证代码的正确性。
打包:将第三方组件和业务本身的代码一起打到一个部署包里。
从上面可以看出,如果要对第三方组件进行数据收集和管控,最好的时机是在编译阶段。
第三方组件管理的需求
前面提到,引入第三方组件存在风险和问题,这些也是影响软件交付质量的重要因素。关于对第三方组件的管理,我们是在现有的 DevOps 平台中增加了依赖管理的功能。主要的功能项目如下图所示。
依赖扫描:能够扫描系统的依赖数据并上传到服务器。需要存储组件的坐标信息,并按系统名、版本进行分类。当时我们是基于 Maven 的插件体系进行的扩展,现在已经有现成的依赖扫描插件了,参考:gemnasium-maven-plugin。
依赖查询:能够查询依赖组件的坐标信息,并按系统名、版本进行检索。
依赖管理:能够维护依赖组件的坐标信息,黑白名单,控制策略和影响范围。黑名单指不允许使用的组件。白名单是可以使用的组件。控制策略指的是当发现组件有问题时采取的措施,如阻断,警告。影响范围指的该控制策略的应用范围,如当前系统,所有系统等。
依赖控制:能够在编译阶段对依赖组件进行检查,当检测到依赖组件中包含了黑名单中的组件,按照设置好的控制策略对该组件进行处理,比如编译失败,邮件通知负责人等。
依赖同步:能够从远程仓库同步依赖组件的版本信息,判断该组件版本是否过时。
反向依赖:能够按依赖组件查询应用系统。当发现组件有问题时,判断该组件的影响范围时非常有用。
第三方组件管理的设计
为了实现上面的功能需求,下面从架构设计、流程图设计、类图设计和接口设计四个方面对第三方组件管理服务进行说明。通过这四个方面的介绍,我相信你基本上就可以按照这个设计编码实现第三方组件管理功能了。下面的图形主要展示的是设计的关键部分,在具体实现的过程中,可以根据实际情况进行扩展。
架构设计
架构设计是从整体上介绍该功能包含哪些组件,以及组件之间的调用关系。第三方组件管理这个功能涉及的核心组件有两个:依赖管理服务和持续集成平台。
& 依赖管理服务
用于维护依赖组件的信息,包含存储和查询依赖组件;添加、修改和删除依赖组件的控制策略;同步依赖组件的最新版本;为前端提供数据查询接口等。
& 持续集成平台
用于在编译阶段对依赖组件进行扫描和扫描数据上报,当发现有黑名单中的依赖组件时,根据依赖组件的控制策略进行处理。
流程图设计
根据上面架构图的设计,下面介绍一下业务流程图,如下图所示,包含2个主要的流程:数据维护流程和依赖扫描流程。
& 数据维护流程
该流程比较简单,就是准备数据。比如安全部门发现fastjson<=1.2.62存在远程代码执行的漏洞,公司里任何系统都不允许依赖该范围的版本。这时就可以将该依赖组件添加到黑名单,groupId 为com.alibaba,artifactId 为fastjson,version 为1.2.62,逻辑运算符为**<=,控制策略为阻断**,应用范围为所有。
& 依赖扫描流程
这一部分是该功能的核心流程。由持续集成平台发起,分为以下几个步骤:
1.克隆代码库,编译源代码;
2.利用 maven 插件,执行依赖扫描任务,获取依赖组件信息;
3.调用依赖管理服务的接口获取黑名单列表;
4.判断扫描的依赖组件中是否包含黑名单中的组件;
5.如果有,则判断控制策略是否是阻断;
6.如果是阻断,则直接阻断当前任务,编译失败;
7.如果是警告,则输出警告信息,上传编译数据,编译通过;
8.如果不包含,则上传依赖数据,编译通过。
类图设计
下面介绍一下该功能涉及的几个关键类,如下图所示,主要有:DmService(服务类)、DmScanRecord(扫描记录类)、DmScanDependency(扫描依赖类)、DmDependency(依赖类)和 DmBlackList(黑名单类)。这几个类的用途是:
DmService(服务类):该类用于存储业务系统与代码库的关联关系。当进行反向依赖查询时,能知道该依赖组件有哪些系统在使用。
DmScanRecord(扫描记录类):该类用于存储每次的依赖扫描记录。为了能知道每次代码提交的依赖组件信息,一般要跟代码库、分支、CommitId 进行关联。
DmScanDependency(扫描依赖类):该类用于存储每次扫描的依赖组件信息,包含层次关系。当发现该组件有问题时,对该组件进行标记。为了减少存储的数据量,这里只存储依赖组件的ID信息,具体详细信息存储在 DmDependency 类中。
DmDependency(依赖类):该类用于存储具体的依赖组件信息,通过 groupId、artifactId、version、classifier 来标记唯一组件。
DmBlackList(黑名单类):该类用于存储依赖组件的黑名单信息。
接口设计
经过上面几个设计图,第三方组件管理功能基本上已经很清晰了,知道了有哪些组件以及组件的调用关系、数据维护和依赖扫描的流程、以及需要哪些类来存储这些元数据。那么,接口设计这部分,就是要基于这些元数据,定义对外暴露的接口,将上面的流程串联起来,最终实现组件间的相互调用。
下面是按照 OpenApi 3.0 的规范编写的接口文档。数据维护相关的有:服务管理和黑名单管理接口。依赖扫描相关的有:黑名单查询和依赖上报接口。查询相关的有:依赖查询和反向依赖查询等。
这些就是第三方组件管理功能主要的设计内容,功能不是很复杂,但能达到有效管理和控制第三方组件的效果。通过嵌入到持续集成流程里,可以作为代码预检查的检查项,尽早发现有问题的组件。
总结
本课时以第三方依赖组件的常见问题开头,引申出要进行第三方依赖组件管理的缘由,并结合自己的实践经验,介绍了依赖管控的方式和时机,提出了第三方依赖组件管理的功能需求,并从架构设计、流程图设计、类图设计和接口设计几部分图文并茂地介绍了如何实现该功能。
在软件开发中,基于组件的设计是一种良好的架构设计,不仅降低了系统之间的耦合性,组件的重用也加快了大型软件的开发速度。随着组件越来越多,已经呈现一种“泛滥”的趋势,组件的质量参差不齐,这也是影响研发进度和软件质量的因素。根据之前的经验,第三方组件的管理一直没有被重视起来,哪些组件能用,哪些组件不能用,大多还是采用口头和文档的形式进行控制。希望这个课时能够引起大家对第三方组件的重视,如果你的企业里也有前面提到的组件问题,也希望这个方案能够帮到你。