订单状态流转是交易系统的最为核心的功能,复杂的订单系统会存在状态多、链路长、逻辑复杂的特点,针对不同的商品、业务、发货方式还存在多场景、多类型、多业务维度等业务特性。在保证订单状态流转稳定性的前提下、可扩展性和可维护性由是需要重点关注和解决的问题。
以公司目前的订单系统为例,订单状态有待支付、支付成功、订单取消、订单关闭等;订单类型有积分订单、商城订单、充值订单、礼品卡订单、付费会员订单等几种类型;业务场景普通订单、供应商订单等场景。
当订单状态、类型、场景、以及其他一些维度组合时,每一种组合都可能会有不同的处理逻辑、也可能会存在共性的业务逻辑,处理这种"多状态+多类型+多场景+多维度"的复杂订单状态流转业务,又要保证整个系统的可扩展性和可维护性。
主要的设计思路为采用模版、策略方法隔离业务复用功能组件,纵向主要从业务隔离和流程编排的角度解决问题、而横向主要从逻辑复用和业务扩展的角度解决问题。
1 纵向解决业务隔离和流程编排
通常处理一个多状态或者多维度的业务逻辑,都会采用状态模式或者策略模式来解决,抽象一个基础逻辑接口、每一个状态或者类型都实现该接口,业务处理时根据不同的状态或者类型调用对应的业务实现,以到达逻辑相互独立互不干扰、代码隔离的目的。
单一状态或类型可以通过上面的方法解决,针对"多状态+多类型+多场景+多维度"这种组合业务也可以采用这种模式或思路来解决。首先通过一个注解@OrderPorcessor将不同的维度予以组合、开发出多个对应的具体实现类,在系统运行阶段,通过判断上下文来动态选择具体使用哪一个实现类执行。@OrderPorcessor中分别定义state代表当前处理器要处理的状态,bizCode和scene分别代表业务类型和场景,这两个字段留给业务进行扩展,比如可以用bizCode代表产品或订单类型、scene代表业务形态。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Component
public @interface OrderProcessor {
/**
* 指定状态,state不能同时存在
*/
OrderStatus[] state();
/**
* 业务
*/
OrderType[] orderType() default {};
/**
* 场景
*/
Scene[] scene() default {};
/**
* 订单操作事件
*/
OrderEventType event();
}
public BaseResult<T> action(OrderContext<C> context) throws Exception {
//校验
BaseResult<T> checkResult = this.check(context);
if (!checkResult.isSuccess()) {
return checkResult;
}
//准备数据
this.prepare(context);
//获取当前状态处理器处理完毕后,所处于的下一个状态
OrderStatus nextState = this.getNextState(context);
//状态动作方法,主要状态迁移逻辑
BaseResult<T> actionResult = this.action(nextState, context);
if (!actionResult.isSuccess()) {
return actionResult;
}
//状态数据持久化
BaseResult<T> result = this.save(nextState, context);
// 状态迁移成功,持久化后执行的后续处理
if (result.isSuccess()) {
TransactionUtil.doAfterCommitTransaction(t -> this.after(context));
}
return result;
}
2 横向解决逻辑复用和实现业务扩展
在真正编码的时候会发现不同类型不同维度对于同一个状态的流程处理过程,有时多个处理逻辑中的一部分流程一样的或者是相似的,甚至有些时候多个类型间的处理逻辑大部分是相同的而差异是小部分,比如下单流程的处理逻辑基本逻辑都差不多(锁、校验状态&参数、处理、保存、同步订单到es、发送消息、设置定时任务等),而付vip、svip、分销价格的不同,充值和购物订单流程、库存管理、发货处理形式等个别流程的差异。对于这种情况就是要实现在纵向解决业务隔离和流程编排的基础上,需要支持小部分逻辑或代码段的复用、或者大部分流程的复用,减少重复开发。
具体的实现方式一是通过继承重写的方式,先实现一个默认的处理器,把所有的标准处理流程和可扩展点进行封装实现、其他处理器进行继承、覆写、替换就好;实现方式二是通过在内容上下文初始化前后、在功能业务处理前后以及持久化前后植入拓展点,通过@OrderProcessor实现的插件,进行匹配筛选需要具体处理器执行的插件并执行。
public BaseResult<T> action(OrderContext<C> context) throws Exception {
List<OrderPostProcessor> supportProcessor = defaultPluginRegistry.getSupportProcessor(context);
log.info("获取插件,插件数量:{},插件列表:{}", supportProcessor.size(), supportProcessor);
List<OrderPreparePostProcessor> orderPreparePostProcessors = supportProcessor
.stream()
.filter(item -> OrderPreparePostProcessor.class.isAssignableFrom(item.getClass()) && item.isOpen(context.getOrderProcessParam().getScene()))
.map(item -> (OrderPreparePostProcessor) item)
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(orderPreparePostProcessors)) {
orderPreparePostProcessors.forEach(item -> item.orderPostProcessBeforePrepare(context));
}
BaseResult<T> checkResult = this.check(context);
if (!checkResult.isSuccess()) {
return checkResult;
}
this.prepare(context);
.........
}