面向复杂业务场景的微中台框架设计实践

2022年11月02日 1,318次浏览

陈德付(星翼)

一、介绍前言

在面对平时的业务功需求迭代时,相信大家都会面临一个代码复杂度和后续的维护高成本的问题,下面我们主要针对营销返利活动场景,对复杂的业务场景的代码设计进行详细的讲解。

二、微中台研发复杂度背景

1、负责的业务中,规则比较多,大部分情况大家是不是会不自觉的试图使用if...else...解决一切问题,这样就会存在一个问题,逻辑在日常的需求迭代中是不是会变的越来越复杂?越来越乱?可读性越来越差?最终慢慢的就会演变成一个看不懂,改不动的黑盒子,没有人搞清楚黑盒子里面到底发生了什么?测试的可测性也大大的降低。
2、在实际的业务开发中,业务场景多,迭代频繁,变化快,规则可能由很多人掌握,没有办法通过一个人了解整个业务规则的全貌的。还有加上业务在行业固有的复杂度和历史包袱,这些问题都会让我们感到痛苦。不知道在哪里改代码,如何下手,改了这块代码会不会带来其他的影响,影响面无法评估。
3、每次需求迭代,研发人力成本投入过多,测试回归成本较高,对应业务需求支撑比较慢,目前比较繁重,对新业务的支持,需要大量的人力成本投入。

三、针对营销活动产品框架

  • 在了解设计背景下,先看下目前主推的营销返利活动的几个玩法。
活动类型描述
单品返利购买指定活动商品,进行活动返利。
普通累返累计活动期限内,按照下单量或者下单GMV,进行奖励累计发放。
实货累返活动推广门店购买提货卡,后面再转成实货的时候,再返点,让门店自提优惠券,或系统自动发放对应奖励。
年框累返以年为单位,进行签署奖励合同,按照下单GMV为坎级算返点,按转实货部分进行算奖励,自提发放优惠券。
抽奖达到一定条件,给用户抽奖机会。
  • 了解了上面的活动玩法,针对产品框架设计,总结出如下图的统一的活动处理产品中台架构设计。

四、架构框架演变升级过程

原代码设计,在面对业务需求功能开发时

  1. 直接烟囱式开发,没有组件沉淀
  2. 逻辑全部耦合在各自的service中,业务代码复用度不高,
  3. 针对不同场景的活动玩法,没有统一消息入口。

如下图:

这样的代码开发设计,就是上面所说的带来的弊端。

  1. 每次业务需求迭代,改动的地方比较大,成本高,不知道从何处下手
  2. 影响面评估难度大,导致测试回归的工作量也变大。

于是针对这类的问题做了概念上的抽象和代码框架的设计:
返利型业务的抽象,实际本质就是了解每种业务玩法特性公共点的抽象,app层从上到下过程分析,模型层从下而上分析结合。能力下层保持模型不断演进,能力下层标准:复用,内聚。
我们举个例子:

需求描述case1: 在活动期间内(10.1-10.7),累计购买金额达到1000元,活动结束后返100元优惠券 。case2: 预热期间有报名,并且在活动期间内(10.1-10.7),累计购买实货(提货卡除外)金额达到1000元,活动结束后返100元优惠券。case3: 在活动期间内(10.1-10.7),累计购买实货(提货卡除外)金额达到1000元,活动结束后第3天返100元优惠券,并可以抽奖一次。caseN: ....
需求分析过程1. 语义分解,抽象,从case描述看我们大致可以抽象为,怎么累计,怎么返利。怎么累还可以细拆为何时,何人等。怎么返还可以细拆为何时返,返什么,返多少等等。
a. "在活动期间内(10.1-10.7)","预热期间有报名" → 前置条件
b. "累计购买金额" → 执行累计
c. "达到1000元","活动结束后" → 返利前置条件
d. 返100元优惠券 → 执行返利
2. 系统流程,框架,模型能力层次抽象,如下图

于是就推演升级出现有架构模式,收敛消息入口,抽象大的的业务领域模块,沉淀通用组件,不同业务域的解耦,提高内部业务逻辑的复用性和高内聚。抽象一套业务执行框架,让开发更专注每个业务需求本身的业务逻辑代码开发。如下图:

整体业务模块的系统架构:

沉淀通用组件,在复用通用场景组件时,只需编排已有组件,更快的支持业务需求的迭代。

五、详细代码设计实现

代码业务框架和业务逻辑分离,实际业务开发中更关注业务模块的开发。详细对应代码设计实现模型:

框架骨架代码执行类图和业务模块核心类图和模型关系:

骨架框架业务执行时序图:

框架骨架核心代码实现伪代码:

/**
 * <功能介绍><br>
 * <p>
 * <>
 *
 * @author xy on 2022/2/25.
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
@Service
public class RebateBizOptServiceImpl implements RebateBizOptService {
 
    private static final YtLogger LOGGER = YtLoggerFactory.getLogger(RebateBizOptServiceImpl.class);
 
    private static final Long SMALL_AMOUNT = 1L;
 
    @Resource
    private RebateRefundShareAmountComponent rebateRefundShareAmountComponent;
 
    @Resource
    private CacheProxy cacheProxy;
 
    @Resource
    private RebateBizExecutor rebateBizExecutor;
 
    /**
     * 门店自提现金券发奖处理
     *
     * @param rebateActivityGiftExtract 提取优惠券参数
     *
     * @return 处理结果
     */
    @Override
    public Boolean rebateActivityGitExtract(RebateActivityGiftExtractDTO rebateActivityGiftExtract) {
        RebateActivityGiftExtractValidator.validate(rebateActivityGiftExtract);
        rebateBizExecutor.execute(MultiRebateCmdDTO.builder()
                                                   .rebateSceneCode(RebateSceneEnum.PRIZE.getSceneCode())
                                                   .rebateActivityGiftExtract(rebateActivityGiftExtract)
                                                   .async(RebateSceneEnum.PRIZE.getAsync())
                                                   .build());
        return Boolean.TRUE;
    }
 
    /**
     * 交易订单支付完成,计算流水处理
     *
     * @param trade           支付交易订单信息
     * @param rebateBizHandle 业务模块
     */
    @Override
    public void rebateActForPaid(TradeDTO trade, Integer rebateBizHandle) {
        rebateBizExecutor.execute(MultiRebateCmdDTO.builder()
                                                   .rebateSceneCode(RebateSceneEnum.PAID.getSceneCode())
                                                   .trade(trade)
                                                   .async(RebateSceneEnum.PAID.getAsync())
                                                   .rebateBizHandle(rebateBizHandle)
                                                   .build()
        );
    }
 
    /**
     * 退款关闭,返利逻辑处理
     *
     * @param orderRefund     退款订单信息
     * @param rebateBizHandle 业务模块
     *
     * @return 处理结果
     */
    @Override
    public void rebateActForRefundClose(OrderRefundDTO orderRefund, Integer rebateBizHandle) {
        rebateBizExecutor.execute(MultiRebateCmdDTO.builder()
                                                   .rebateSceneCode(RebateSceneEnum.REFUND_CLOSED.getSceneCode())
                                                   .orderRefund(orderRefund)
                                                   .async(RebateSceneEnum.REFUND_CLOSED.getAsync())
                                                   .rebateBizHandle(rebateBizHandle)
                                                   .build()
        );
    }
}
/**
 * <功能介绍><br>
 * <p>
 * <返利业务执行器>
 *
 * @author xy on 2022/6/14.
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
@Service
public class RebateBizExecutor {
 
    private static final YtLogger LOGGER = YtLoggerFactory.getLogger(RebateBizExecutor.class);
 
    @Resource
    private SkeletonHandleBuildBiz skeletonHandleBuildBiz;
 
    @Resource
    private DefaultRebateHandleFrameBuilder defaultRebateHandleFrameBuilder;
 
    @Resource
    private YearRebateHandleFrameBuilder yearRebateHandleFrameBuilder;
 
    @Resource
    private MaterialRebateHandleFrameBuilder materialRebateHandleFrameBuilder;
 
    /**
     * 返利中台骨架业务执行
     *
     * @param multiRebateCmd 执行参数
     *
     * @return 处理结果
     */
    public List<MultiRebateResult> execute(MultiRebateCmdDTO multiRebateCmd) {
        //请求参数检查验证
        validateRebateCmdParams(multiRebateCmd);
 
        //初始化上下文信息
        MultiRebateContext context = MultiRebateContextDelegation.initContext(multiRebateCmd);
 
        //获取通用资源数据
        fetchCommonResource(context);
 
        //加载返利骨架窗口
        loadHandleFrame(context);
 
        //执行骨架构建和业务模块处理
        List<MultiRebateResult> resultList = skeletonHandleBuildBiz.buildHandles(context);
 
        //返回结果解析聚合
        return assembleResults(resultList, multiRebateCmd);
    }
 
    /**
     * 返回结果解析聚合
     *
     * @param resultList     业务执行结果
     * @param multiRebateCmd 请求入参
     *
     * @return 解析后的处理结果
     */
    private List<MultiRebateResult> assembleResults(List<MultiRebateResult> resultList, MultiRebateCmdDTO multiRebateCmd) {
        List<MultiRebateResult> failResults = StreamUtil.filter(resultList, BaseRebateResult::filterFail);
        if (CollectionUtils.isEmpty(failResults)) {
            return resultList;
        }
        LOGGER.src(REBATE_BIZ_FRAMEWORK).warn("开始执行返利业务处理,参数信息:{},处理失败结果:{}", JSONObject.toJSONString(multiRebateCmd), JSONObject.toJSONString(failResults));
        throw new SmcException(Boolean.TRUE, failResults.get(0).getMessage());
    }
 
    /**
     * 加载返利骨架窗口
     *
     * @param context 上下文信息
     */
    private void loadHandleFrame(MultiRebateContext context) {
        HandleFrame handleFrame;
        if (RebateBizHandleEnum.YEAR_HANDLE.eq(context.getRebateBizHandle())
                || (RebateSceneEnum.PRIZE.equal(context.getRebateSceneCode())) && PromotionActTypeEnum.REBATE_ACTIVITY.equal(context.getRebateActivityGiftExtract().getActType())) {
            handleFrame = yearRebateHandleFrameBuilder.build(context);
        } else if (RebateBizHandleEnum.MATERIAL_HANDLE.eq(context.getRebateBizHandle())
                || (RebateSceneEnum.PRIZE.equal(context.getRebateSceneCode())) && PromotionActTypeEnum.MATERIAL_REBATE.equal(context.getRebateActivityGiftExtract().getActType())) {
            handleFrame = materialRebateHandleFrameBuilder.build(context);
        } else {
            handleFrame = defaultRebateHandleFrameBuilder.build(context);
        }
        context.setHandleFrame(handleFrame);
    }
 
    /**
     * 获取通用资源数据
     *
     * @param context 上下文信息
     */
    private void fetchCommonResource(MultiRebateContext context) {
    }
 
    /**
     * 请求参数检查验证
     *
     * @param multiRebateCmd 请求参数指令
     */
    private void validateRebateCmdParams(MultiRebateCmdDTO multiRebateCmd) {
        SmcValidate.notNull(multiRebateCmd, "处理请求参数不能为空");
    }
/**
 * <功能介绍><br>
 * <p>
 * <默认返利模块构建器>
 *
 * @author xy on 2022/6/14.
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
@Component
public class DefaultRebateHandleFrameBuilder implements RebateHandleFrameBuilder {
 
    @Resource
    private MaterialMultiRebateHandle materialMultiRebateHandle;
 
    @Resource
    private YearMultiRebateHandle yearMultiRebateHandle;
 
    @Override
    public HandleFrame build(MultiRebateContext rebateContext) {
        return HandleFrame.instance()
                          .addConfig(HandleFrame.HandleConfig.of(materialMultiRebateHandle.getIdentifier()))
                          .addConfig(HandleFrame.HandleConfig.of(yearMultiRebateHandle.getIdentifier()));
    }
}
/**
 * <功能介绍><br>
 * <p>
 * <处理器构建业务执行>
 *
 * @author xy on 2022/6/14.
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
@SuppressWarnings("ALL")
@Component
public class SkeletonHandleBuildBiz {
 
    private static final YtLogger LOGGER = YtLoggerFactory.getLogger(SkeletonHandleBuildBiz.class);
 
    @Resource
    private AsdThreadPool rebateHandleThreadPool;
 
    /**
     * 构架构建并执行业务模块
     *
     * @param context 上下文信息
     *
     * @return 执行结果
     */
    public List<MultiRebateResult> buildHandles(MultiRebateContext context) {
        try {
            List<Callable<MultiRebateResult>> callableList = buildCallableList(context);
 
            List<Future<MultiRebateResult>> futures = rebateHandleThreadPool.invokeAll(callableList);
            if (CollectionUtils.isEmpty(futures)) {
                return Lists.newArrayList();
            }
 
            List<MultiRebateResult> rebateResults = Lists.newArrayList();
            for (Future<MultiRebateResult> future : futures) {
                rebateResults.add(future.get());
            }
            return rebateResults;
        } catch (InterruptedException | ExecutionException e) {
            LOGGER.src(REBATE_BIZ_FRAMEWORK).error("并行执行骨架业务处理异常,请求参数:{}", JSONObject.toJSONString(context), e);
        }
        return Lists.newArrayList();
    }
 
    private List<Callable<MultiRebateResult>> buildCallableList(MultiRebateContext context) {
        List<Callable<MultiRebateResult>> callableList = Lists.newArrayList();
        for (HandleFrame.HandleConfig handleConfig : context.getHandleFrame().getHandles()) {
 
            RebateHandle rebateHandle = getRebateHandle(handleConfig.getIdentifier());
            RebateMatcher rebateMatcher = getRebateMatcher(handleConfig.getMatcherIdentifier());
            RebateProcessor rebateProcessor = getRebateProcessor(handleConfig.getProcessorIdentifier());
            RebateExtension rebateExtension = getRebateExtension(handleConfig.getExtensionIdentifier());
 
            callableList.add(new Callable<MultiRebateResult>() {
                @Override
                public MultiRebateResult call() throws Exception {
                    MultiRebateResult result = buildSingleHandle(context, rebateHandle, rebateProcessor, rebateMatcher, rebateExtension);
                    return Optional.ofNullable(result).orElseGet(() ->
                            MultiRebateResult.buildFailResult(handleConfig.getIdentifier() + "模块未返回结果")
                    );
                }
            });
        }
        return callableList;
    }
 
    private MultiRebateResult buildSingleHandle(BaseRebateContext context,
                                    RebateHandle<BaseRebateHandleDTO, BaseRebateContext, MultiRebateResult> handle,
                                    RebateProcessor<BaseRebateHandleDTO, MultiRebateResult> processor,
                                    RebateMatcher<BaseRebateHandleDTO, BaseRebateContext> matcher,
                                    RebateExtension<BaseRebateHandleDTO> extension) {
        try {
            return (MultiRebateResult) handle.execute(context, matcher, processor, extension);
        } catch (DeadlockLoserDataAccessException e) {
            LOGGER.src(REBATE_BIZ_FRAMEWORK).warn("handle:{},执行处理异常.获取数据库锁失败", handle.getIdentifier(), e);
            return MultiRebateResult.buildFailResult("获取锁失败");
        } catch (DuplicateKeyException e) {
            LOGGER.src(REBATE_BIZ_FRAMEWORK).warn("handle:{},执行处理异常.唯一键冲突,重复处理", handle.getIdentifier(), e);
            return MultiRebateResult.buildFailResult(RebateResultEnum.NOT_CONFORM_RULE_ACT_FAIL);
        } catch (Throwable e) {
            LOGGER.src(REBATE_BIZ_FRAMEWORK).error("handle:{},执行处理异常.", handle.getIdentifier(), e);
            return MultiRebateResult.buildFailResult(e.getMessage());
        }
    }
 
    private RebateHandle getRebateHandle(String handleId) {
        return StringUtils.isBlank(handleId) ? null : RebateHandleIndex.getHandleIndex().get(handleId);
    }
 
    private RebateMatcher getRebateMatcher(String matcherId) {
        return StringUtils.isBlank(matcherId) ? null : RebateHandleIndex.getMatcherIndex().get(matcherId);
    }
 
    private RebateProcessor getRebateProcessor(String processorId) {
        return StringUtils.isBlank(processorId) ? null : RebateHandleIndex.getProcessorIndex().get(processorId);
    }
 
    private RebateExtension getRebateExtension(String extensionId) {
        return StringUtils.isBlank(extensionId) ? null : RebateHandleIndex.getExtensionIndex().get(extensionId);
    }
}
/**
 * <功能介绍><br>
 * <p>
 * <基础返利处理器>
 *
 * @author xy on 2022/6/14.
 * @see [相关类/方法](可选)
 * @since [产品/模块版本] (可选)
 */
public interface RebateHandle<H extends BaseRebateHandleDTO, C extends BaseRebateContext, R> extends ClassNameIdentifiable {
 
    YtLogger LOGGER = YtLoggerFactory.getLogger(RebateHandle.class);
 
    @SuppressWarnings("AlibabaThreadShouldSetName")
    ExecutorService EXECUTOR = new ThreadPoolExecutor(20, 30, 0L,
                                                        TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(2048));
 
    /**
     * 执行业务
     *
     * @param context   上下文参数
     * @param matcher   返利匹配器
     * @param processor 返利处理器
     * @param extension 扩展处理器
     *
     * @return 执行结果
     */
    default R execute(@NotNull C context, @Nullable RebateMatcher<H, C> matcher, @Nullable RebateProcessor<H, R> processor,
                      RebateExtension<H> extension) {
 
        if (Objects.isNull(matcher) || Objects.isNull(processor)) {
            return null;
        }
 
        H matchHandleDTO = matcher.match(context);
 
        matchHandleDTO = post(matchHandleDTO, context);
 
        R process = processor.process(matchHandleDTO);
 
        Optional.ofNullable(matchHandleDTO).ifPresent(o -> Optional.ofNullable(extension).ifPresent(x -> {
            if (BooleanUtils.isTrue(o.getAsync())) {
                EXECUTOR.execute(() -> expand(x, o));
            } else {
                expand(x, o);
            }
        }));
 
        return process;
    }
 
    /**
     * 内部转换器
     *
     * @param handleDTO  数据模型
     * @param context    上下文参数
     *
     * @return 处理结果
     */
    default H post(H handleDTO, C context) {
        if (Objects.isNull(handleDTO)) {
            return null;
        }
 
        handleDTO.setRebateSceneCode(context.getRebateSceneCode());
        handleDTO.setAsync(context.getAsync());
 
        return handleDTO;
    }
 
    /**
     * 扩展逻辑执行
     *
     * @param extension 扩展器
     * @param handle    数据模型
     */
    default void expand(RebateExtension<H> extension, H handle) {
        try {
            extension.expand(handle);
        } catch (Throwable e) {
            LOGGER.src(REBATE_BIZ_FRAMEWORK).error("执行后置扩展器:{} 执行异常,参数信息:{}", extension.getIdentifier(), JSONObject.toJSONString(handle), e);
        }
    }
}