前言
日常我们在项目开发中经常会进行项目迭代,比如说开发初期设定的代码逻辑根据功能需求迭代逐渐发现越来越难用,或者改动是对整体较大时,往往会进行专项处理,对这个逻辑进行改造。
那么就会涉及到原先被调用方切换接口等问题,这种情况我们为了让外部接口无感知,都是采用内部切流的方式进行外部无感知的。
那么我们来构造一个场景来看看如何进行切流和代码改造。
正文
我们有几个逻辑需要改造的接口
@Service
public class ARepository {@Resourceprivate AMapper aMapper;public AEntity query(long id){//原有的接口逻辑处理}
}@Service
public class BRepository {@Resourceprivate BMapper bMapper;public List<BEntity> query(long id){//原有的接口逻辑处理}
}
思路
- 以接口的维度来控制每一个接口是否要进行切流
- 新建一个同级别的新接口实现改造后的逻辑,避免新老逻辑在同一个文件里混杂
- 以配置的形式来判断是否需要切换,若出现问题可单独进行回溯
实现方式
apollo
我们创建一个可以随时改动的配置,我这里使用 apollo 进行配置以便随时改动和发布生效:
每一个 Repository 有唯一一个 key 值,然后去 value 里面找方法 key。
布尔值 false 表示走原逻辑、true 表示走改造后的逻辑。
{"ARepository":{"query":false},"BRepository":{"query":false}
}
新逻辑
@Service
public class NewARepository {@Resourceprivate AMapper aMapper;public AEntity query(long id){//新的接口逻辑处理}
}@Service
public class NewBRepository {@Resourceprivate BMapper bMapper;public List<BEntity> query(long id){//新的接口逻辑处理}
}
切流控制
创建一个切流控制类,使用上面的 apollo 来判断是否需要执行新逻辑并返回。
我们构造一个泛型类,统一注入新逻辑类,在调用 switchHandler 是传入新逻辑函数,通过 apollo 判断是否需要执行
@Slf4j
@Getter
@Component
public class MapperSwitchHandler {@Resourceprivate NewARepository newARepository;@Resourceprivate NewBRepository newARepository;//lombok忽略此属性,不生成 get 方法@Getter(AccessLevel.NONE)//获取 apollo 配置信息@ApolloJsonValue(namespace = "namespace", config = "config", key = "key")private JSONObject mapperSwitch;/*** @Author: zhou* @Description: 根据传入参数获取 apollo 配置的 boolean 值* @Date: 2024/5/27 11:38* @Param: className apollo 中类名--一级* @Param: methodName apollo 中方法名--二级* @return: boolean*/private boolean switchResult(String className, String methodName) {if (mapperSwitch == null) {return false;}return Optional.ofNullable(mapperSwitch.getJSONObject(className)).map(item -> item.getBoolean(methodName)).orElse(false);}/*** @Author: zhou* @Description: 根据 apollo 配置的 boolean 值来判断是否执行传入的 action 函数,并获取结果* @Date: 2024/5/27 11:36* @Param: className apollo 中类名* @Param: methodName apollo 中方法名* @Param: action 待执行函数* @return: MapperSwitchHandler.Result<T>*/public <T> Result<T> switchHandler(String className, String methodName, Supplier action) {Result<T> result = new Result<>();boolean flag = switchResult(className, methodName);log.info("className:{}, methodName:{}, mapper switch : {}", className, methodName, flag);result.setFlag(flag);if (result.isFlag()) {result.setData((T) action.get());log.info("new logic result :{}", JSONObject.toJSONString(result.getData()));} else {log.info("old logic start...");}return result;}@Slf4j@Datapublic static class Result<T> {/*** 逻辑标识*/private boolean flag;/*** 获取的结果*/private T data;}
}
这样我们就实现好了新逻辑的控制和改造,现在需要对原逻辑进行改造判断。
老逻辑改造
@Service
public class ARepository {@Resourceprivate AMapper aMapper;//注入开关@Resourceprivate MapperSwitchHandler mapperSwitchHandler;public AEntity query(long id){MapperSwitchHandler.Result<AEntity> result = mapperSwitchHandler.switchHandler("ARepository","query",() -> mapperSwitchHandler.getNewARepository().query(id));// flag 判断是否走新 sql ,true 标识走新 sql,直接返回;false 标识走老逻辑if (result.isFlag()) {return result.getData();}//原有的接口逻辑处理}
}@Service
public class BRepository {@Resourceprivate BMapper bMapper;//注入开关@Resourceprivate MapperSwitchHandler mapperSwitchHandler;public List<BEntity> query(long id){MapperSwitchHandler.Result<List<BEntity>> result = mapperSwitchHandler.switchHandler("BRepository","query",() -> mapperSwitchHandler.getNewBRepository().query(id));// flag 判断是否走新 sql ,true 标识走新 sql,直接返回;false 标识走老逻辑if (result.isFlag()) {return result.getData();}//原有的接口逻辑处理}
}
这样我们就实现了通过 apollo 配置来灵活的判断每一个接口层面的逻辑控制。待灰度完成或者新逻辑全部正确的时候我们有两种方式来进行代码清理:
- 完全删掉老的类文件,原调用方只需要重新引用新的接口类
- 只删除老逻辑,老接口的入口调用新逻辑即可。
尾言
本文介绍了在进行代码改造进行逻辑切流失的逻辑操作,通过 apollo 配置来实时动态的判断各个接口的控制效果,通过统一的处理类避免了每一个需要改造的代码都需要引用新的逻辑类和开关配置。总体来说是一个比较不错的方案,第一次实现此场景,如果更好的建议可以交流一下。