陈德付(星翼)
一、介绍前言
在面对平时的业务功需求迭代时,相信大家都会面临一个代码复杂度和后续的维护高成本的问题,下面我们主要针对营销返利活动场景,对复杂的业务场景的代码设计进行详细的讲解。
二、微中台研发复杂度背景
1、负责的业务中,规则比较多,大部分情况大家是不是会不自觉的试图使用if...else...解决一切问题,这样就会存在一个问题,逻辑在日常的需求迭代中是不是会变的越来越复杂?越来越乱?可读性越来越差?最终慢慢的就会演变成一个看不懂,改不动的黑盒子,没有人搞清楚黑盒子里面到底发生了什么?测试的可测性也大大的降低。
2、在实际的业务开发中,业务场景多,迭代频繁,变化快,规则可能由很多人掌握,没有办法通过一个人了解整个业务规则的全貌的。还有加上业务在行业固有的复杂度和历史包袱,这些问题都会让我们感到痛苦。不知道在哪里改代码,如何下手,改了这块代码会不会带来其他的影响,影响面无法评估。
3、每次需求迭代,研发人力成本投入过多,测试回归成本较高,对应业务需求支撑比较慢,目前比较繁重,对新业务的支持,需要大量的人力成本投入。
三、针对营销活动产品框架
- 在了解设计背景下,先看下目前主推的营销返利活动的几个玩法。
活动类型 | 描述 |
---|---|
单品返利 | 购买指定活动商品,进行活动返利。 |
普通累返 | 累计活动期限内,按照下单量或者下单GMV,进行奖励累计发放。 |
实货累返 | 活动推广门店购买提货卡,后面再转成实货的时候,再返点,让门店自提优惠券,或系统自动发放对应奖励。 |
年框累返 | 以年为单位,进行签署奖励合同,按照下单GMV为坎级算返点,按转实货部分进行算奖励,自提发放优惠券。 |
抽奖 | 达到一定条件,给用户抽奖机会。 |
- 了解了上面的活动玩法,针对产品框架设计,总结出如下图的统一的活动处理产品中台架构设计。
四、架构框架演变升级过程
原代码设计,在面对业务需求功能开发时
- 直接烟囱式开发,没有组件沉淀
- 逻辑全部耦合在各自的service中,业务代码复用度不高,
- 针对不同场景的活动玩法,没有统一消息入口。
如下图:
这样的代码开发设计,就是上面所说的带来的弊端。
- 每次业务需求迭代,改动的地方比较大,成本高,不知道从何处下手
- 影响面评估难度大,导致测试回归的工作量也变大。
于是针对这类的问题做了概念上的抽象和代码框架的设计:
返利型业务的抽象,实际本质就是了解每种业务玩法特性公共点的抽象,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);
}
}
}