Fork me on GitHub

设计模式的优雅:函数式pipeline+策略模式实现复杂业务@落雨

现在有一个新需求,要求对老接口进行升级,原有逻辑基础上做功能路由,识别老业务走老接口,命中新业务(灰度)则走新接口,且新老接口出入参焕然一新,完全不同,但是要保证原有接口出入参一致(相当于强行换轮子还不要影响线上业务,前端都无需改动)。你会怎么设计?本篇文章提供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

posted @ 2021-09-10 20:13  _落雨  阅读(888)  评论(0编辑  收藏  举报