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

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

陈德付(星翼)

一、介绍前言

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

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

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. 系统流程,框架,模型能力层次抽象,如下图

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

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

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

五、详细代码设计实现

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

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

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

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

  1. /**
  2. * <功能介绍><br>
  3. * <p>
  4. * <>
  5. *
  6. * @author xy on 2022/2/25.
  7. * @see [相关类/方法](可选)
  8. * @since [产品/模块版本] (可选)
  9. */
  10. @Service
  11. public class RebateBizOptServiceImpl implements RebateBizOptService {

  12. private static final YtLogger LOGGER = YtLoggerFactory.getLogger(RebateBizOptServiceImpl.class);

  13. private static final Long SMALL_AMOUNT = 1L;

  14. @Resource
  15. private RebateRefundShareAmountComponent rebateRefundShareAmountComponent;

  16. @Resource
  17. private CacheProxy cacheProxy;

  18. @Resource
  19. private RebateBizExecutor rebateBizExecutor;

  20. /**
  21. * 门店自提现金券发奖处理
  22. *
  23. * @param rebateActivityGiftExtract 提取优惠券参数
  24. *
  25. * @return 处理结果
  26. */
  27. @Override
  28. public Boolean rebateActivityGitExtract(RebateActivityGiftExtractDTO rebateActivityGiftExtract) {
  29. RebateActivityGiftExtractValidator.validate(rebateActivityGiftExtract);
  30. rebateBizExecutor.execute(MultiRebateCmdDTO.builder()
  31. .rebateSceneCode(RebateSceneEnum.PRIZE.getSceneCode())
  32. .rebateActivityGiftExtract(rebateActivityGiftExtract)
  33. .async(RebateSceneEnum.PRIZE.getAsync())
  34. .build());
  35. return Boolean.TRUE;
  36. }

  37. /**
  38. * 交易订单支付完成,计算流水处理
  39. *
  40. * @param trade 支付交易订单信息
  41. * @param rebateBizHandle 业务模块
  42. */
  43. @Override
  44. public void rebateActForPaid(TradeDTO trade, Integer rebateBizHandle) {
  45. rebateBizExecutor.execute(MultiRebateCmdDTO.builder()
  46. .rebateSceneCode(RebateSceneEnum.PAID.getSceneCode())
  47. .trade(trade)
  48. .async(RebateSceneEnum.PAID.getAsync())
  49. .rebateBizHandle(rebateBizHandle)
  50. .build()
  51. );
  52. }

  53. /**
  54. * 退款关闭,返利逻辑处理
  55. *
  56. * @param orderRefund 退款订单信息
  57. * @param rebateBizHandle 业务模块
  58. *
  59. * @return 处理结果
  60. */
  61. @Override
  62. public void rebateActForRefundClose(OrderRefundDTO orderRefund, Integer rebateBizHandle) {
  63. rebateBizExecutor.execute(MultiRebateCmdDTO.builder()
  64. .rebateSceneCode(RebateSceneEnum.REFUND_CLOSED.getSceneCode())
  65. .orderRefund(orderRefund)
  66. .async(RebateSceneEnum.REFUND_CLOSED.getAsync())
  67. .rebateBizHandle(rebateBizHandle)
  68. .build()
  69. );
  70. }
  71. }
  1. /**
  2. * <功能介绍><br>
  3. * <p>
  4. * <返利业务执行器>
  5. *
  6. * @author xy on 2022/6/14.
  7. * @see [相关类/方法](可选)
  8. * @since [产品/模块版本] (可选)
  9. */
  10. @Service
  11. public class RebateBizExecutor {

  12. private static final YtLogger LOGGER = YtLoggerFactory.getLogger(RebateBizExecutor.class);

  13. @Resource
  14. private SkeletonHandleBuildBiz skeletonHandleBuildBiz;

  15. @Resource
  16. private DefaultRebateHandleFrameBuilder defaultRebateHandleFrameBuilder;

  17. @Resource
  18. private YearRebateHandleFrameBuilder yearRebateHandleFrameBuilder;

  19. @Resource
  20. private MaterialRebateHandleFrameBuilder materialRebateHandleFrameBuilder;

  21. /**
  22. * 返利中台骨架业务执行
  23. *
  24. * @param multiRebateCmd 执行参数
  25. *
  26. * @return 处理结果
  27. */
  28. public List<MultiRebateResult> execute(MultiRebateCmdDTO multiRebateCmd) {
  29. //请求参数检查验证
  30. validateRebateCmdParams(multiRebateCmd);

  31. //初始化上下文信息
  32. MultiRebateContext context = MultiRebateContextDelegation.initContext(multiRebateCmd);

  33. //获取通用资源数据
  34. fetchCommonResource(context);

  35. //加载返利骨架窗口
  36. loadHandleFrame(context);

  37. //执行骨架构建和业务模块处理
  38. List<MultiRebateResult> resultList = skeletonHandleBuildBiz.buildHandles(context);

  39. //返回结果解析聚合
  40. return assembleResults(resultList, multiRebateCmd);
  41. }

  42. /**
  43. * 返回结果解析聚合
  44. *
  45. * @param resultList 业务执行结果
  46. * @param multiRebateCmd 请求入参
  47. *
  48. * @return 解析后的处理结果
  49. */
  50. private List<MultiRebateResult> assembleResults(List<MultiRebateResult> resultList, MultiRebateCmdDTO multiRebateCmd) {
  51. List<MultiRebateResult> failResults = StreamUtil.filter(resultList, BaseRebateResult::filterFail);
  52. if (CollectionUtils.isEmpty(failResults)) {
  53. return resultList;
  54. }
  55. LOGGER.src(REBATE_BIZ_FRAMEWORK).warn("开始执行返利业务处理,参数信息:{},处理失败结果:{}", JSONObject.toJSONString(multiRebateCmd), JSONObject.toJSONString(failResults));
  56. throw new SmcException(Boolean.TRUE, failResults.get(0).getMessage());
  57. }

  58. /**
  59. * 加载返利骨架窗口
  60. *
  61. * @param context 上下文信息
  62. */
  63. private void loadHandleFrame(MultiRebateContext context) {
  64. HandleFrame handleFrame;
  65. if (RebateBizHandleEnum.YEAR_HANDLE.eq(context.getRebateBizHandle())
  66. || (RebateSceneEnum.PRIZE.equal(context.getRebateSceneCode())) && PromotionActTypeEnum.REBATE_ACTIVITY.equal(context.getRebateActivityGiftExtract().getActType())) {
  67. handleFrame = yearRebateHandleFrameBuilder.build(context);
  68. } else if (RebateBizHandleEnum.MATERIAL_HANDLE.eq(context.getRebateBizHandle())
  69. || (RebateSceneEnum.PRIZE.equal(context.getRebateSceneCode())) && PromotionActTypeEnum.MATERIAL_REBATE.equal(context.getRebateActivityGiftExtract().getActType())) {
  70. handleFrame = materialRebateHandleFrameBuilder.build(context);
  71. } else {
  72. handleFrame = defaultRebateHandleFrameBuilder.build(context);
  73. }
  74. context.setHandleFrame(handleFrame);
  75. }

  76. /**
  77. * 获取通用资源数据
  78. *
  79. * @param context 上下文信息
  80. */
  81. private void fetchCommonResource(MultiRebateContext context) {
  82. }

  83. /**
  84. * 请求参数检查验证
  85. *
  86. * @param multiRebateCmd 请求参数指令
  87. */
  88. private void validateRebateCmdParams(MultiRebateCmdDTO multiRebateCmd) {
  89. SmcValidate.notNull(multiRebateCmd, "处理请求参数不能为空");
  90. }
  1. /**
  2. * <功能介绍><br>
  3. * <p>
  4. * <默认返利模块构建器>
  5. *
  6. * @author xy on 2022/6/14.
  7. * @see [相关类/方法](可选)
  8. * @since [产品/模块版本] (可选)
  9. */
  10. @Component
  11. public class DefaultRebateHandleFrameBuilder implements RebateHandleFrameBuilder {

  12. @Resource
  13. private MaterialMultiRebateHandle materialMultiRebateHandle;

  14. @Resource
  15. private YearMultiRebateHandle yearMultiRebateHandle;

  16. @Override
  17. public HandleFrame build(MultiRebateContext rebateContext) {
  18. return HandleFrame.instance()
  19. .addConfig(HandleFrame.HandleConfig.of(materialMultiRebateHandle.getIdentifier()))
  20. .addConfig(HandleFrame.HandleConfig.of(yearMultiRebateHandle.getIdentifier()));
  21. }
  22. }
  1. /**
  2. * <功能介绍><br>
  3. * <p>
  4. * <处理器构建业务执行>
  5. *
  6. * @author xy on 2022/6/14.
  7. * @see [相关类/方法](可选)
  8. * @since [产品/模块版本] (可选)
  9. */
  10. @SuppressWarnings("ALL")
  11. @Component
  12. public class SkeletonHandleBuildBiz {

  13. private static final YtLogger LOGGER = YtLoggerFactory.getLogger(SkeletonHandleBuildBiz.class);

  14. @Resource
  15. private AsdThreadPool rebateHandleThreadPool;

  16. /**
  17. * 构架构建并执行业务模块
  18. *
  19. * @param context 上下文信息
  20. *
  21. * @return 执行结果
  22. */
  23. public List<MultiRebateResult> buildHandles(MultiRebateContext context) {
  24. try {
  25. List<Callable<MultiRebateResult>> callableList = buildCallableList(context);

  26. List<Future<MultiRebateResult>> futures = rebateHandleThreadPool.invokeAll(callableList);
  27. if (CollectionUtils.isEmpty(futures)) {
  28. return Lists.newArrayList();
  29. }

  30. List<MultiRebateResult> rebateResults = Lists.newArrayList();
  31. for (Future<MultiRebateResult> future : futures) {
  32. rebateResults.add(future.get());
  33. }
  34. return rebateResults;
  35. } catch (InterruptedException | ExecutionException e) {
  36. LOGGER.src(REBATE_BIZ_FRAMEWORK).error("并行执行骨架业务处理异常,请求参数:{}", JSONObject.toJSONString(context), e);
  37. }
  38. return Lists.newArrayList();
  39. }

  40. private List<Callable<MultiRebateResult>> buildCallableList(MultiRebateContext context) {
  41. List<Callable<MultiRebateResult>> callableList = Lists.newArrayList();
  42. for (HandleFrame.HandleConfig handleConfig : context.getHandleFrame().getHandles()) {

  43. RebateHandle rebateHandle = getRebateHandle(handleConfig.getIdentifier());
  44. RebateMatcher rebateMatcher = getRebateMatcher(handleConfig.getMatcherIdentifier());
  45. RebateProcessor rebateProcessor = getRebateProcessor(handleConfig.getProcessorIdentifier());
  46. RebateExtension rebateExtension = getRebateExtension(handleConfig.getExtensionIdentifier());

  47. callableList.add(new Callable<MultiRebateResult>() {
  48. @Override
  49. public MultiRebateResult call() throws Exception {
  50. MultiRebateResult result = buildSingleHandle(context, rebateHandle, rebateProcessor, rebateMatcher, rebateExtension);
  51. return Optional.ofNullable(result).orElseGet(() ->
  52. MultiRebateResult.buildFailResult(handleConfig.getIdentifier() + "模块未返回结果")
  53. );
  54. }
  55. });
  56. }
  57. return callableList;
  58. }

  59. private MultiRebateResult buildSingleHandle(BaseRebateContext context,
  60. RebateHandle<BaseRebateHandleDTO, BaseRebateContext, MultiRebateResult> handle,
  61. RebateProcessor<BaseRebateHandleDTO, MultiRebateResult> processor,
  62. RebateMatcher<BaseRebateHandleDTO, BaseRebateContext> matcher,
  63. RebateExtension<BaseRebateHandleDTO> extension) {
  64. try {
  65. return (MultiRebateResult) handle.execute(context, matcher, processor, extension);
  66. } catch (DeadlockLoserDataAccessException e) {
  67. LOGGER.src(REBATE_BIZ_FRAMEWORK).warn("handle:{},执行处理异常.获取数据库锁失败", handle.getIdentifier(), e);
  68. return MultiRebateResult.buildFailResult("获取锁失败");
  69. } catch (DuplicateKeyException e) {
  70. LOGGER.src(REBATE_BIZ_FRAMEWORK).warn("handle:{},执行处理异常.唯一键冲突,重复处理", handle.getIdentifier(), e);
  71. return MultiRebateResult.buildFailResult(RebateResultEnum.NOT_CONFORM_RULE_ACT_FAIL);
  72. } catch (Throwable e) {
  73. LOGGER.src(REBATE_BIZ_FRAMEWORK).error("handle:{},执行处理异常.", handle.getIdentifier(), e);
  74. return MultiRebateResult.buildFailResult(e.getMessage());
  75. }
  76. }

  77. private RebateHandle getRebateHandle(String handleId) {
  78. return StringUtils.isBlank(handleId) ? null : RebateHandleIndex.getHandleIndex().get(handleId);
  79. }

  80. private RebateMatcher getRebateMatcher(String matcherId) {
  81. return StringUtils.isBlank(matcherId) ? null : RebateHandleIndex.getMatcherIndex().get(matcherId);
  82. }

  83. private RebateProcessor getRebateProcessor(String processorId) {
  84. return StringUtils.isBlank(processorId) ? null : RebateHandleIndex.getProcessorIndex().get(processorId);
  85. }

  86. private RebateExtension getRebateExtension(String extensionId) {
  87. return StringUtils.isBlank(extensionId) ? null : RebateHandleIndex.getExtensionIndex().get(extensionId);
  88. }
  89. }
  1. /**
  2. * <功能介绍><br>
  3. * <p>
  4. * <基础返利处理器>
  5. *
  6. * @author xy on 2022/6/14.
  7. * @see [相关类/方法](可选)
  8. * @since [产品/模块版本] (可选)
  9. */
  10. public interface RebateHandle<H extends BaseRebateHandleDTO, C extends BaseRebateContext, R> extends ClassNameIdentifiable {

  11. YtLogger LOGGER = YtLoggerFactory.getLogger(RebateHandle.class);

  12. @SuppressWarnings("AlibabaThreadShouldSetName")
  13. ExecutorService EXECUTOR = new ThreadPoolExecutor(20, 30, 0L,
  14. TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(2048));

  15. /**
  16. * 执行业务
  17. *
  18. * @param context 上下文参数
  19. * @param matcher 返利匹配器
  20. * @param processor 返利处理器
  21. * @param extension 扩展处理器
  22. *
  23. * @return 执行结果
  24. */
  25. default R execute(@NotNull C context, @Nullable RebateMatcher<H, C> matcher, @Nullable RebateProcessor<H, R> processor,
  26. RebateExtension<H> extension) {

  27. if (Objects.isNull(matcher) || Objects.isNull(processor)) {
  28. return null;
  29. }

  30. H matchHandleDTO = matcher.match(context);

  31. matchHandleDTO = post(matchHandleDTO, context);

  32. R process = processor.process(matchHandleDTO);

  33. Optional.ofNullable(matchHandleDTO).ifPresent(o -> Optional.ofNullable(extension).ifPresent(x -> {
  34. if (BooleanUtils.isTrue(o.getAsync())) {
  35. EXECUTOR.execute(() -> expand(x, o));
  36. } else {
  37. expand(x, o);
  38. }
  39. }));

  40. return process;
  41. }

  42. /**
  43. * 内部转换器
  44. *
  45. * @param handleDTO 数据模型
  46. * @param context 上下文参数
  47. *
  48. * @return 处理结果
  49. */
  50. default H post(H handleDTO, C context) {
  51. if (Objects.isNull(handleDTO)) {
  52. return null;
  53. }

  54. handleDTO.setRebateSceneCode(context.getRebateSceneCode());
  55. handleDTO.setAsync(context.getAsync());

  56. return handleDTO;
  57. }

  58. /**
  59. * 扩展逻辑执行
  60. *
  61. * @param extension 扩展器
  62. * @param handle 数据模型
  63. */
  64. default void expand(RebateExtension<H> extension, H handle) {
  65. try {
  66. extension.expand(handle);
  67. } catch (Throwable e) {
  68. LOGGER.src(REBATE_BIZ_FRAMEWORK).error("执行后置扩展器:{} 执行异常,参数信息:{}", extension.getIdentifier(), JSONObject.toJSONString(handle), e);
  69. }
  70. }
  71. }