現在有一個新需求,要求對老接口進行升級,原有邏輯基礎上做功能路由,識別老業務走老接口,命中新業務(灰度)則走新接口,且新老接口出入參煥然一新,完全不同,但是要保證原有接口出入參一致(相當於強行換輪子還不要影響線上業務,前端都無需改動)。你會怎么設計?本篇文章提供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流程(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 {
/**
* 獲取結果(pipe組裝,交給子類實現)
* @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 完事,很清爽.
有兄弟問我,ItemResultContext
是干啥的,這個里面就是個上下文對象,里面可以放Anything,比如入參、出參對象等,方便Function執行的時候(Function<ItemResultContext/**入參類型T**/, ItemResultContext>/**出參類型T**/)
,上下傳遞,是統一的最外層容器。
@Data
public class ItemResultContext implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 老模型上下文
*/
private ItemContext itemContext;
/**
* 新模型上下文
*/
private XItemContext xItemContext;
}
落雨 2021-09-10 20:12:45
http://www.js-dev.cn