上一篇谈了怎样给版本取个好名字,也就是版本号。说明了“语义化版本”的命名规范,也说明了这一种命名规范在依赖管理中发挥的重要做用。今天继续谈语义化版本号,说明一下这种命名方式的重要性,以及对研发和运维过程的影响。web
开发人员每一次向下游工序交付一个版本,都必须为这个版本编一个号码,这就是版本号(也称做版本 ID)。若是每次交付都是一样的版本号,随着时间的推动就会产生不少版本号相同、可是功能不同的二进制包,在这种状况下部署,你能够想象会遇到多少惊奇。对于每一次生成二进制包,都应该分配一个惟一标识,对于审计来讲,这是很是重要的。最显而易见的方法,是使用二进制包的散列值做为惟一标识,以即可以验证生成二进制包的源代码是否正确。当你不肯定某个环境到底部署了哪一个版本的时候,可使用文件的 MD5 码找出版本库中对应的版本。一些二进制产物管理平台能够自动提取散列码。可是以散列码做为标识有一些缺点,很明显的一个缺点是散列码太长,与他人沟通的时候很难记得住(你很难告诉同事“把 customer 组件从 ea3304ea2fff21dd1e501795c43c48ff 版本替换成 81b134b598b16d1605e6f76189f1c018 版本就能够解决你遇到的问题”,他也很难记住)。更重要的是散列码只能标识两个版本是否相同,却没法体现版本之间的时间关系(哪个版本是新的)和兼容性(新版本是否彻底包含老版本的功能)。因此咱们应该使用规范化的版本号做为二进制包的标识符。网络
因此咱们必须采用语义化版本规范,使用三段式版本号,以下:数据结构
主版本号.次版本号.修订号
版本号递增规则以下:运维
使用这种方式,版本号就再也不是一个随意的命名。任何一个以 API 方式对外提供服务的程序(不管是 Web API、消息处理、函数 API),都应该遵循语义化版本规范。从 API 规范的意义上说,语义化版本其实是对软件接口规格的描述。例如一个软件模块对外提供 Web API 服务,模块版本是 2.3.1,能够理解成这样的规格描述:函数
为了准确描述程序的接口规范,咱们在设计和开发的时候要尽可能把接口规范和实现代码分离,这样就能够更准确的控制 API 规格。以 Web API 为例,咱们能够把与服务接口相关的代码放在单独的目录里,好比把控制器代码所有放在 controller 目录,输入输出数据结构所有在 vo 目录。这样就能够在发布版本的时候根据变动的范围准确肯定版本号。工具
当如下变动发生时,接口的调用方法发生了变化,须要升级主版本号:ui
当如下变动发生时,原有的接口仍然能够工做,须要升级次版本号:spa
当如下变动发生时,只须要升级修订号:操作系统
用这样的方法,版本号就能够描述程序内部的变动范围。 设计
下面说一下版本号对依赖管理的做用。在构建和运行软件时,软件的一部分要依赖于另外一部分,就产生了依赖关系。在任何应用程序(甚至是最小的应用程序)中也会有一些依赖关系。至少,大多数软件应用都对其运行的操做系统环境有依赖,Java 应用程序依赖于 JVM,它提供了 JavaSE API 的一个实现。网络服务之间也存在依赖关系。在大型软件中,从组件中选择好用的版本,组成一个完整的系统是一个极具难度的事。为了作好依赖管理,咱们必须作下面几件事:
咱们以 Linux 操做系统为例看一下依赖管理的过程。Linux 是一个很是复杂的体系,它自己由不少二进制包组成,使用者也须要在操做系统上安装本身须要的程序。若是没有一个依赖管理机制,要在 Linux 上安装一个软件,将会是一件困难的任务。幸运的是各类 Linux 发行版都提供了完善的包管理机制,还附带了包管理工具。好比 Debian 操做系统,提供了 dpkg 工具,如下是使用 dpkg 查看 wget 信息:
$ dpkg -s wget Package: wget Section: web Maintainer: Noël Köthe <noel@debian.org> Architecture: amd64 Version: 1.18-5+deb9u3 Depends: libc6 (>= 2.17), libgnutls30 (>= 3.5.6), libidn11 (>= 1.13), libnettle6, libpcre3, libpsl5 (>= 0.13.0), libuuid1 (>= 2.16), zlib1g (>= 1:1.1.4)
这里列出了主要信息,有两个信息很是重要:
有了这些信息,就能够在安装 wget 的时候检查 Debian 上已经安装的库,判断是否知足依赖条件,包管理工具能够级联安装全部的依赖项。也能够检查 wget 与已经安装的程序是否存在依赖冲突,提示用户进行处理。若是没有这一套包管理机制,在 Linux 上安装一个包是很是冒险的事情。
最后咱们再来看看语义化版本是怎样帮助咱们作好老版本维护的。有时候正在生产环境运行的老版本突然发现一个缺陷,或者须要添加一个功能,都须要对老版本进行维护。这种事情在 To B 业务很是多见,To B 业务部署在不少现场,每一个现场项目实施的时期不同,因此版本都有一些差别,对老版本进行维护是一件不可避免的事情。
若是不使用语义化版本号,好比用一个不断增加的序号来标识版本号,连续发布多个版本就会造成这样的版本路径:
随着时间的发展,有一些老版本会在部署在不一样的现场。如今 1002 版本上发现一个缺陷,须要紧急修复。这时候该怎么办呢?1002 版本已经通过了 2 次升级,直接替换成 1004 版本行不行,很难判断,因此只能基于 1002 版本升级替换。这个好办,使用 Git 作一个分支,修改后从新发布一个版本就能够了。缺陷修改后,造成下面这样的版本路径:
若是之后须要维护 100一、1003 版本,继续发展下去,版本路径就会愈来愈复杂:
维护版本分支愈来愈多,基本上要为每个老版本建立一个维护分支,工做量随着项目发展愈来愈大。开发团队要把大量的精力放在老项目维护上,产品开发的工做受到愈来愈多的牵制。语义化版本号能怎样改变这种局面呢?若是每一次发布都按照语义化版本编号,那么最初的版本路径就是下面这样:
用这种方式,咱们就能准确判断版本之间的兼容关系,根据版本之间的替换关系肯定最佳维护位置。当咱们在 1.0.6 版本上发现一个缺陷,须要紧急修复,1.2.1 版本能够彻底兼容 1.0.6 版本的功能。若是这个缺陷已经在 1.2.1 版本获得修复,那么升级现场的版本便可。若是必须修改代码,也只须要在 1.2.1 的基础上修改,再发布一个版本便可,不增长维护分支:
若是已经发生了主版本升级的状况,咱们也只须要为每个主版本建立一个维护分支,就能同时知足多个项目的维护工做,下降维护工做量。
如上图,对于全部 1.x 主版本,只须要基于 1.2.2 版本创建一个维护分支便可。