1 需求
一般情况下,一个服务具有测试环境、线上沙箱环境(beta环境)和生产环境三种过度环境。对于那些需要多个服务共同合作的项目,服务双方需要在相应的环境下进行测试通过才能正式投入线上运行。常规的测试流程是:测试环境联调--> beta环境联调--> 生产环境正式上线。如下图所示:
上线步骤.png但如果服务目前并不具备独立的线上Beta环境的话,和那些具备Beta环境的服务合作的时候,服务Beta环境的缺失会给双方联调测试带来诸多问题。服务方解决这个问题的常见方法如下所述(假设有两个服务,称为A和B,其中服务A没有Beta环境,服务B有Beta环境):
- 服务B调用服务A的测试环境服务
这种情况下,如果服务B的Beta环境需要的是生产环境的数据(线上线下的数据往往是隔离的),那么服务A的测试环境提供的服务将无法满足其需求。
- 服务B调用服务A的生产环境服务
这种情况下,如果服务A的生产环境新作的修改会导致当前生产环境的状态发生变化,而服务B又没有正式上线(Beta环境和生产环境是隔离的),且服务A的正常服务需要服务B的支持,那么服务A直接上线就会影响线上的正常服务。
另一方面,对于一个服务来说,如果短期内有多个项目需要上线,但是其中的一个项目因为某些原因不能在生产环境中生效,那么就会影响后续项目的上线计划。有如下两种方式解决这个问题:
- 该项目不上线,其他项目并行开发,优先考虑当前能够上线的功能。但是如果项目之间有共同的修改(譬如说添加了某一通用模块,该模块在后续项目中也可以使用),那么后续的项目将无法共享其修改。
- 简单的添加一个开关,满足条件的话采用新的修改。但是这个方法会在线上环境中引入
新旧不同的版本
,功能修改过程中,需要保留原始逻辑。
为了不影响后续的开发,服务中需要添加功能上线版本控制模块
,该模块的目的是:
- 解决和拥有Beta环境的服务共同联调的困难,支撑调用发或者依赖方在Beta环境上的服务。
- 能够对上线的功能进行版本控制,支持特定功能延迟上线的需求
2 思路
直接引入开关变量
虽然能够满足大体的需求,但是该方法没有统一的模板,如果新功能中修改的地方比较分散,比较容易出错。因此,可以在开关变量
的基础上,抽象出功能上线版本控制模块
。
-
功能版本的分类
对于一个新的项目,其所提供的功能的状态无非是
原始版本
、新版本
和混合版本
三种:-
原始版本
:新功能上线前的状态 -
新版本
:新功能替换原始功能 -
混合版本
:线上同时运行新旧两种版本,只不过新版本的调用需要满足一定的条件,例如白名单
引入
上线步骤-混合版本.png混合版本
后,就可以通过提供一个混合版本中的过滤条件
控制线上功能的服务状态。当满足过滤条件的时候,调用新版本的服务,当不满足条件的时候,依旧调用老版本的服务。可以将此过滤条件理解为类似白名单的作用。 -
-
功能版本的切换
版本的切换可以通过一个简单的
上线步骤-控制流程图.png状态值
进行控制,控制流程如下:
版本切换的逻辑在任何功能中都是一致的,因此这部分逻辑是通用逻辑,可以通过设计模式中的模板方法
实现这个逻辑,也可以通过接口中的默认函数
实现这个逻辑。
-
版本不同状态的封装
既然需要控制新功能在线上的情况,在开发新功能的时候,就需要将原始的功能代码保留下来,与新的更新的功能一起被提取出来,作为新旧两个版本对外提供服务。
这里的难点在于如何提供通用的入参(不同功能的参数肯定不一样)和通用的返回对象。为了尽可能的灵活性,采用Java中的泛型机制
控制入参和返回。
3 实现
3.1 版本状态变量
通过枚举类定义版本控制的三种状态
public enum FunctionVersionStatusEnum {
APPLY_ORIGINAL_VERSION(0, "应用原逻辑"),
APPLY_MIXED_VERSION(1, "应用混合逻辑"),
APPLY_NEW_VERSION(2, "应用新逻辑");
// ... 省略其他
}
目前,功能的版本控制在配置文件中设置,在类中读取并转化成FunctionVersionStatusEnum
对象
3.2 版本控制通用模板接口
通过接口定义功能上线版本控制
模块中需要实现的方法,通过接口的默认方法模拟模板方法
完成版本控制逻辑。
public interface FunctionVersionControl<T, R> {
R applyNewVersion(T request);
R applyOriginalVersion(T request);
boolean checkNewVersionConditionInMixedVersionModel(T request);
FunctionVersionStatusEnum getStatus();
default R apply(FunctionVersionStatusEnum versionEnum, T request) {
Objects.requireNonNull(versionEnum, "versionEnum can not be null");
switch(versionEnum) {
case APPLY_NEW_VERSION:
return applyNewVersion(request);
case APPLY_MIXED_VERSION:
if (checkNewVersionConditionInMixedVersionModel(request)) {
return applyNewVersion(request);
}
return applyOriginalVersion(request);
case APPLY_ORIGINAL_VERSION:
return applyOriginalVersion(request);
default:
throw new AssertionError("FunctionVersionStatusEnum doesn't have type " + versionEnum);
}
}
}
其中,applyNewVersion
和applyOriginalVersion
方法分别表示功能的新旧逻辑;checkNewVersionConditionInMixedVersionModel
方法为混合版本
模式下能够触发新版本功能
的前提条件;getStatus
方法用于获取当前功能的状态;apply
方法应用版本控制逻辑。
如果是新功能,则
applyOriginalVersion
为空方法即可
如果有白名单类似的需求,重写checkNewVersionConditionInMixedVersionModel
方法,让能够调用新版本逻辑
的部分结果为true
4 使用
实现功能上线版本控制
的方式有两种:
- 直接在相关的类中实现
FunctionVersionControl
接口,调用apply
方法 - 新建类实现
FunctionVersionControl
接口,注入该类并调用其apply
网友评论