配置文件是什么
随着业务的发展,程序的功能复杂度也在增加,我们需要开关某些功能、变更功能的参数范围、修改业务的文案、调整界面的显示参数等等,这些功能的变更点都可以称作配置项。
小萌新把这些配置直接硬编码在代码中,每次都勤勤恳恳地找到对应的代码进行修改;后来,老油条把这些配置提取成配置文件,应用通过加载配置文件就完成了业务功能的开关控制,变更时只要找到对应配置项修改一下即可。
因此,我们可以知道配置文件是一个集中记录了配置项的文件,它会影响应用的行为,即同一份程序代码在不同的配置文件下会有不同的行为。
大家发现只要改改配置文件就可以改变 APP 的行为表现真是太 awosone 了,干脆把配置文件拿到服务器上,这样更新配置文件后,通过网络下发给应用,就不用再麻烦开发工程师变更发布应用版本了。看起来,配置文件简直是公司产品运营的上帝啊,只需要程序员发一个应用版本,以后就不需要程序员了。
当然,很显然这种事情是不可能的,因为应用的变更实际有不同的可能,从应用的逻辑来看都发生了变更,但不同的变更可以有不同的处理方式。
增删应用的功能:应用的功能增删一定需要代码的变更,没程序员可不行,这种情况需要发布新版本的 APP。
应用的功能范围变更:应用的某个功能或业务需要调整范围,例如开关某个功能、指定优惠活动业务的地区城市、增减商品的种类等,这种情况通常就只需要更新配置文件。
因此,配置文件是为了满足应用不重出版本而动态改变业务逻辑而存在的,也可以说配置文件的存在是为了扩展性需要。
配置文件管理问题
配置文件的好处显而易见,从端到端出发,还有一些问题需要考虑。
从服务器后端来看:
本人没开发过服务器,相关内容不作展开,还请有经验的人士多多指教。
完善的权限控制:
由于配置文件可以改变应用的行为,如果配置出现问题可能引起灾难。因此配置文件的更新需要有权限控制,上线生产环境前要确保新配置文件经过测试环境的验证。
高效的配置管理
配置中心是为了方便开发或运维下发配置文件的,不同环境(开发环境、测试环境、生产环境)、不同集群(异地数据中心)可能配置文件不同,配置文件资源的管理一定要方便高效。
可靠的服务
配置中心作为基础服务,可用性要求非常高。配置文件上线后发现新严重问题,服务器要有兜底能力。
从应用前端来看:
合理地获取与使用
对配置文件和资源的获取要考虑流量问题。且配置对于程序是只读的,程序通过读取配置来改变自己的行为,但是程序不应该去改变配置。
兼容稳定性
配置文件的获取要与应用本向相匹配,且应用要对配置文件的内容有容错机制,尽量保证应用的稳定性、兼容性不受影响。另外在服务器不可用情况下也要有相应的处理。
从配置文件本身来看:
灵活扩展性
配置文件的内容的结构设计要考虑灵活统一,不能像大杂烩,也不能有太多限制,否则配置文件就无法迭代。
配置文件的版本管理
获取配置文件并加载使用的过程相对于 App 而言,类似于从服务器提获取数据,然后解析返回的数据。服务器获取数据的 API 都有变更的需求,配置文件变更的可能性就更大了,当配置文件中字段的修改或增加导致必须要修改代码才可支持,那么就认为新配置文件中该设备与老版本 APK 不兼容。
因此类比于 API 的版本控制,来思考一下配置文件下发版本的控制。API 的版本控制有三种常见的方式:
无版本控制
指的是仅有一个版本,不保证兼容性,所有客户端必须使用最新的版本,即客户端来适配 API。这种情况下 API 的行为变更会影响到整个平台的用户。
兼容性版本控制
指的是平台仅有一个版本,但新版本要兼容旧版本的行为。
多版本控制
指的是平台有多版本,使用不同版本号来区分,客户端根据需求使用,需要获取对应的版本。
通常情况下应用会从简单处理的角度出发希望做配置文件的兼容性版本控制。在此处理下通常新增功能字段不会影响旧版本 APK 的处理,因为旧版本 APK 会忽略新字段。但对字段内容进行新增或更新可能会导致功能与实际情况不符。
在实际情况中,无版本控制基本是不可取的,而兼容性版本控制对 API 设计、代码功能设计能力要求很高,且可能出现多次兼容修改后无法继续迭代,最终走向多版本控制。即使使用了多版本控制,在接口设计时,同样要注意扩展能力的保留,尽量避免频繁不兼容的变更,而且多版本控制给 API 设计留出的灵活度较大,但需要维护多套配置,维护成本还是比较大。
多版本共存基本上跑不了,那多版本共存的方式怎么选择呢?
配置文件多版本控制方式
如果你是做服务端平台的,会有不同的用户来使用你的 API,客户端范围本身也是变化的,加上用户更新不及时,就必然会遗留多个版本,因此服务端设计多版本共存时要提供给客户端主动指定版本的方式。常用的有以下方式:
- URL 路径中添加不同版本:
base.com/v1/res
&base.com/v2/res
- 子域名:
v1.xx.com
&v2.xx.com
- 参数:
base.com/res?version=v1
&base.com/res?version=v2
- HEADER:
version:1
&version:2
各方案都有各自的优缺点,具体可见链接:App 多版本服务端兼容。
而从单一客户端来说,服务端的多版本 API 的使用给开发维护带得不小的难度,甚至可能是混乱的。例如可能在切换 API 版本过程中,有的接口还不准备换,导致多个版本的 API 共存于代码中,难以分辨,维护改动时需要极度关注细节。
从客户端角度出发,客户端想要从服务器获取为自己定制的数据或配置文件。因此还有一个常用的办法,就是把 API内容的选择控制权交给服务端,由服务端主导。简单来说就是把客户端本身的信息比如应用版本、设备名称、系统版本等传到服务器,服务器解析字段,匹配到对应的 API 版本,返回给客户端,这样客户端使用的永远是自己支持的版本。
例如,客户端获取时,使用如下链接 xx.com/res?appVersion=100&platform=Android
,这就是通过参数隔离的方式,由服务器进行条件匹配针对性下发内容。
这种方式需要服务器支持且服务端配置维护工作量相对比较大,但比起它的收益而言,这是可以接受的。在实践中,我们希望这个服务器对这些参数的匹配可以有相对灵活的配置方式,从而稍微降低维护人员的工作量。例如支持以下特点的匹配规则:
最大化匹配
例如查询的条件参数有 10 个,服务器上有几个配置实例,分别声明了 5 个、4 个、3 个的匹配规则参数;服务器优先与 5 个参数的配置实例匹配,如果能匹配上则返回信息,否则查询 4 个参数的实例能否匹配,如果不能匹配就依次直至最少参数的实例匹配完毕,如果都不匹配就返回空值。
时间优先匹配
如果服务器多个配置实例的匹配参数个数相同,且 APK 查询的参数都满足匹配,那么返回最新上线的配置实例的信息。
我们以如下目标使用客户端上传参数的方式来设计一个方案实例。
目标:
- APP 需要获取配置文件和显示资源
- 不同版本 APP 获取不同配置文件
- 不同平台或不同品牌 APP 获取不同的显示资源
获取配置文件和显示资源
根据 RESTful 概念,这是两种资源,不应该在一个链接中返回,因此,增加参数 query
,即:
- xx.com/res?query=config
- xx.com/res?query=drawable
- xx.com/res?query=language
分别返回 config 下载链接列表和资源的下载链接列表。拆分的好处是配置文件很小,可以直接下载,而不同的资源可以根据需要在适合的时机才进行查询。
不同版本 APP 获取不同配置文件
为了满足对 APP 版本的匹配要求,要增加 APP 版本参数 version
。实际上这里指的是应用的功能版本,不建议直接使用 APP 本身的版本号,因为如果 APP 版本与配置项无关我们仍修改此版本,可能会极大地增加维护人员的工作量。因此我们采用 apiVersion
参数,即由 APP 来定义“API 版本”,如果 APP 的可配置功能发生变更,就更新这个版本号。即:
- xx.com/res?query=config&apiVersion=100
这个版本号理论上只要传一个就可以,如果不兼容就加 1 就好了。不过在 APP 迭代中建议遵循语义化版本的规范:版本格式为 主版本号.次版本号.修订号
,版本号递增规则如下:
- 主版本号:当你做了不兼容的 API 修改,
- 次版本号:当你做了向下兼容的功能性新增,
- 修订号:当你做了向下兼容的问题修正。
为了防止可能的兼容性修改并不兼容,建议使用两个参数 versionMajor
、versionMinor
把主次版本信息传递给服务器,这样在有必要的时候,可以支持区分。因此链接参数定为:
- xx.com/res?query=config&versionMajor=2&versionMinor=0
不同平台或不同品牌 APP 获取不同的显示资源
对应用来说,不同平台或品牌的风格由于多品牌策略可能有所不同,比如设计风格不同,那显示资源就要有所区分。可以考虑增加参数 brand
和 platform
,即最终链接体现为:
- xx.com/res?query=config&versionMajor=2&versionMinor=0&brand=Xiaomi&platform=Android
对参数的制定和扩展大可根据业务自行选择制定。
配置文件内容的版本管理
如果所有的功能开关都由服务器来选择配置,除非服务器支持动态生成配置文件,否则对配置文件 的维护人员而言也是一个很大的考验。
因此我们考虑把配置文件下发给应用,让应用在配置文件中进行识别了。可以在各个列表范围内增加一个功能节点,其中增加一个字段标识此功能哪个版本开始支持。如:
1 | "supportVersion": [ |
配置文件中功能版本号
功能版本号版本号具备两个作用:
- 区分大版本:因为大版本会共存;
- 区分代码版本:代码涉及功能变更时变更的代码版本号。
因此,base_version
分成两段:1000_0004,前一段表示应用大版本,后一段表示代码功能小版本。一个完整的 supportVersion
字段示例如下:
1 | "supportVersion": [ |
该版本号指定的是一种限制,表示限制该大版本下,小版本之后可支持该设备。因此对应的解析规则可作如下设定,我们以设备列表节点中具备多个功能节点为前提:
- 某一设备不添加 supportVersion 限定,则认为所有版本都支持。
- 应用大版本不在限定版本列表中,则认为支持该设备。
- 应用大版本与限定的大版本匹配时,但应用小版本大于限定的小版本时,认为此应用不支持该功能,但支持该设备。若新功能范围不被旧版本支持,请将子版本设定为最大值 9999。
- 格式错误或混入非数字,则应用直接不支持该功能:防错。
结合以上两个方面:配置文件的获取版本控制和配置文件内容的版本控制,基本可以支持应用配置的灵活扩展与兼容稳定。