现在有一个新需求,要求对老接口进行升级,原有逻辑基础上做功能路由,识别老业务走老接口,命中新业务(灰度)则走新接口,且新老接口出入参焕然一新,完全不同,但是要保证原有接口出入参一致(相当于强行换*还不要影响线上业务,前端都无需改动)。你会怎么设计?本篇文章提供2种方式来解决
流程图:
一、常规做法(最简单的玩法)
增加路由处理类,分别路由到原有接口和新接口,由各自接口进行处理
if(老逻辑){
doSomeThingOld(context);
} else {
doSomeThingNew(context);
}
二、优雅做法(抽象、piple、策略)
如果按照常规模式,那么这个需求就结束了,只需在入口层增加一个路由,后面各自的接口自己去实现入参校验、参数组装、逻辑处理、结果变换、log打印,但是你稍加思索就觉得,哎呀,这一套组合拳下来,岂不是一样的流程,很好,我们开始抽象。
2.1 抽出函数处理器,负责执行函数
/**
* Pipe执行器,使用Stream.map处理各层Fuction
* <pre>
Function<ItemResultContext, ItemResultContext>> 的含义是使用一个ItemResultContext入参,传出一个ItemResultContext出参
</pre>
* @param context
* @param functionList
* @return
*/
private Optional<ItemResultContext> buildItemResult(ItemResultContext context,
List<Function<ItemResultContext, ItemResultContext>> functionList) {
// 获取context流,context是每个函数的出入参
Stream<ItemResultContext> stream = Stream.of(context);
if (CollectionUtils.isEmpty(functionList)) {
return Optional.empty();
}
// 同步执行,循环处理函数流, stream1 -> stream2 -> stream3 -> ... streamN
for (Function f : functionList) {
// @link{<R> Stream<R> map(Function<? super T, ? extends R> mapper);}
stream = stream.map(f);
}
return stream.findFirst();
}
2.2 抽出pipeline函数处理器,用来定义流程
/**
* Pipe组装
* @param context
* @param functionList
* @return
*/
public ItemResultContext buildItemResult(ItemResultContext context) {
// 组装流程
List<Function<ItemResultContext, ItemResultContext>> list = Lists.newArrayList();
list.add(logAroundBuildItemResult("covertReq", this::covertReq));
list.add(logAroundBuildItemResult("queryItems", this::queryItems));
list.add(logAroundBuildItemResult("covertItems", this::covertItems));
list.add(logAroundBuildItemResult("queryCategorys", this::queryCategorys));
list.add(logAroundBuildItemResult("combineResult", this::combineResult));
Optional<ItemResultContext> opt = buildItemResult(context, list);
return opt.orElse(context);
}
2.3 抽出log处理,环绕切面,切函数
/**
* Pipe环绕切面,apply -> function
* @param desc
* @param func
* @return
*/
private Function<ItemResultContext, ItemResultContext> logAroundBuildItemResult(String desc,
Function<ItemResultContext, ItemResultContext> func) {
return req -> {
long start = System.currentTimeMillis();
log.error("[{}] start", desc);
ItemResultContext resp = func.apply(req);
log.error("[{}] finish, time elapsed {}ms", desc, System.currentTimeMillis() - start);
return resp;
};
}
2.4 将此函数处理器、切面环绕等基础方法抽到父类,提取抽象方法,让子类来实现函数pipe组装,让子类来实现更多的业务场景
@Slf4j
public abstract class AbstractItemManager implements ItemManager {
/**
* 获取结果
* @param context
* @return
*/
public abstract ItemResultContext buildItemResult(ItemResultContext context);
/**
* 转换req到上下文
* @param context
* @return
*/
public abstract ItemResultContext covertReq(ItemResultContext context);
/**
* 查询商品
* @param context
* @return
*/
public abstract ItemResultContext queryItems(ItemResultContext context);
/**
* 转换结果
* @param context
* @return
*/
public abstract ItemResultContext covertItems(ItemResultContext context);
/**
* 查询类目
* @param context
* @return
*/
public abstract ItemResultContext queryCategorys(ItemResultContext context);
/**
* Pipe执行器
* @param context
* @param functionList
* @return
*/
private Optional<ItemResultContext> buildItemResult(ItemResultContext context,
List<Function<ItemResultContext, ItemResultContext>> functionList) {
// 获取context
Stream<ItemResultContext> stream = Stream.of(context);
if (CollectionUtils.isEmpty(functionList)) {
return Optional.empty();
}
// 函数列表执行,同步执行
for (Function f : functionList) {
stream = stream.map(f);
}
return stream.findFirst();
}
/**
* Pipe环绕切面,apply -> function
* @param desc
* @param func
* @return
*/
private Function<ItemResultContext, ItemResultContext> logAroundBuildItemResult(String desc,
Function<ItemResultContext, ItemResultContext> func) {
return req -> {
long start = System.currentTimeMillis();
log.error("[{}] start", desc);
ItemResultContext resp = func.apply(req);
log.error("[{}] finish, time elapsed {}ms", desc, System.currentTimeMillis() - start);
return resp;
};
}
}
2.5 完事,很清爽
落雨 2021-09-10 20:12:45
http://www.js-dev.cn