作者:京东零售 樊思国

一、布景

在研制项目中,经常能遇到杂乱的状况流通类的事务场景,比方游戏编程中NPC的跳跃、行进、转向等状况变化,电商领域订单的状况变化等。这类状况其实能够有一种优雅的完成办法:状况机。如下图,是操作体系对进程调度的状况机:

面向状态机编程:复杂业务逻辑应对之道

图 操作体系进程调度状况机

二、完成办法

面临以上场景,通常状况下的完成有以下几种,下面别离比较它们适用范围和优缺陷:

2.1 if/else

长处:完成简单、直观。

缺陷:状况多了代码可读性,事务与状况判断深度耦合,保护和扩展困难。

2.2 状况模式

状况模式类图及运用办法如下:

面向状态机编程:复杂业务逻辑应对之道

public class StatePatternDemo {
   public static void main(String[] args) {
      Context context = new Context();
      StartState startState = new StartState();
      startState.doAction(context);
      System.out.println(context.getState().toString());
      StopState stopState = new StopState();
      stopState.doAction(context);
      System.out.println(context.getState().toString());
   }
}

长处:状况单独完成,可读性比if/else好。

缺陷:扩展状况需添加状况类,状况多了会呈现许多状况类;并没有彻底完成状况与事务解耦,不利于保护和了解整个体系状况全貌。

2.3 有限状况机

长处:严谨的数学模型,状况搬运和事务逻辑根据事情彻底解耦,能看到整个体系状况全貌便于保护和扩展。

缺陷:需引进状况机完成办法,具有必定了解本钱。

三、有限状况机

3.1 界说

有限状况机(英语:finite-state machine,缩写:FSM)又称有限状况自动机(英语:finite-state automaton,缩写:FSA),简称状况机,是表明有限个状况以及在这些状况之间的搬运和动作等行为的数学核算模型。

3.2 关键概念

  • 状况State:一般在状况搬运图顶用圆圈表明。
  • 事情Event:表明从一种状况迁移到另一种状况的触发机制。对应状况转化图中的箭头部分。
  • 动作Action: 表明状况转化后,履行的动作,但不是有必要的,也能够状况转化后不履行任何动作。
  • 搬运Transition:表明状况转化,从原始状况迁移到目的状况的一个过程。
  • 条件Guard:表明产生状况搬运需满足的条件。

3.3 技术选型

Java项目中,比较常用的有Spring Statemachine和Squirrel-foundation。

框架 长处 缺陷
Spring Statemachine 根据Spring生态,社区强大。功能齐备,支持多种状况机装备和持久化办法。 较为重量级,额外功能多。单例模式状况机不保证线程安全,只能经过工厂模式创立新的状况机实例完成,对功能有必定影响。
Squirrel-foundation 轻量级完成,状况机的创立开销小。便于二次改造,完成定制事务。 社区没有spring活泼。特别约好较多。

综上,在下面的项目中,由于团队运用SpringBoot作为开发框架,并且项目不触及高并发场景,故选择Spring Statemachine。

四、项目实战

在实践项目中,碰到多种状况转化的杂乱事务流程,能够经过以下几个过程进行分级,逐渐将产品需求明晰的完成出来:

4.1 需求布景

零售采销在保护SKU物流特点(长、宽、高和重量)时,会由于和物流仓库侧实践特点存在不一致的状况,导致带来物流本钱的误差。为处理这个问题,需规划一个体系供采销经过操作SKU的办法,将特点下发给物流侧审阅,得到最终的准确物流特点。在对SKU进行审阅操作的过程中,别离存在未操作、使命下发中、下发失利、已审阅、自行联络供货商和挂起6种状况(状况转化详见4.2),考虑到这些状况转化的条件散布在不同的场景下,处于对可保护性和扩展性的考虑,采用状况机完成该需求。

4.2 状况转化图

经过梳理状况转化联络,画出状况转化图如下:

面向状态机编程:复杂业务逻辑应对之道

SKU特点审阅状况转化图

4.3 装备状况机

4.3.1 界说状况枚举

public enum SkuStateEnum {
    /**
     * 未操作
     */
    INIT(0, "未操作"),
    /**
     * 使命下发中
     */
    TASK_DELIVERY(1, "使命下发中"),
    /**
     * 下发失利
     */
    DELIVERY_FAIL(2, "下发失利"),
    /**
     * 复核中
     */
    RECHECKING(3, "复核中"),
    /**
     * 已复核
     */
    RECHECKED(4, "已复核"),
    /**
     * 自行联络供货商
     */
    CONCAT_SUPPLIER(5, "自行联络供货商"),
    /**
     * 挂起
     */
    SUSPEND(6, "挂起");
    /**
     * 状况代码
     */
    private Integer state;
    /**
     * 描述信息
     */
    private String desc;
    SkuStateEnum(Integer state, String desc) {
        this.state = state;
        this.desc = desc;
    }
    public static SkuStateEnum getByState(Integer state) {
        for (SkuStateEnum skuStateEnum : SkuStateEnum.values()) {
            if (skuStateEnum.getState().equals(state)) {
                return skuStateEnum;
            }
        }
        return null;
    }
    public Integer getState() {
        return state;
    }
    public String getDesc() {
        return desc;
    }
}

4.3.2 界说事情枚举

public enum SkuAttrEventEnum {
    /**
     * 调用OMC特点收集接口成功
     */
    INVOKE_OMC_ATTR_COLLECT_API_SUCCESS,
    /**
     * 调用OMC特点收集接口失利
     */
    INVOKE_OMC_ATTR_COLLECT_API_FAIL,
    /**
     * 调用OMC下发查询接口并现已生成收集单
     */
    INVOKE_OMC_SKU_DELIVERY_API_GATHER_FINISH,
    /**
     * 调用OMC下发查询接口失利
     */
    INVOKE_OMC_SKU_DELIVERY_API_FAIL,
    /**
     * OMC的MQ回来SKU特点已改变
     */
    MQ_OMC_SKU_ATTR_CHANGED,
    /**
     * 调用产品中台jsf接口,回来SKU特点已改变
     */
    INVOKE_SKU_ATTR_API_CHANGED,
    /**
     * 京东有库存
     */
    HAS_JD_STOCK,
    /**
     * 京东无库存,VMI有库存
     */
    NO_JD_STOCK_HAS_VMI_STOCK,
    /**
     * 京东和VMI均无库存
     */
    NO_JD_STOCK_NO_VMI_STOCK,
    /**
     * 上传并复核
     */
    UPLOAD_AND_RECHECK;
}

4.3.3 装备状况机

@Configuration
@EnableStateMachineFactory
@Slf4j
public class SkuAttrStateMachineConfig extends StateMachineConfigurerAdapter<SkuStateEnum, SkuAttrEventEnum> {
    /**
     * 装备状况
     *
     * @param states
     * @throws Exception
     */
    @Override
    public void configure(StateMachineStateConfigurer<SkuStateEnum, SkuAttrEventEnum> states) throws Exception {
        states.withStates().initial(SkuStateEnum.INIT)
            .states(EnumSet.allOf(SkuStateEnum.class));
    }
    @Override
    public void configure(StateMachineConfigurationConfigurer<SkuStateEnum, SkuAttrEventEnum> config) throws Exception {
        config.withConfiguration().listener(listener()).autoStartup(false);
    }
    /**
     * 装备状况转化和事情的联络
     *
     * @param transitions
     * @throws Exception
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<SkuStateEnum, SkuAttrEventEnum> transitions)
        throws Exception {
        transitions.withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.TASK_DELIVERY)
            .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS)
            .action(ctx -> {
                log.info("[调用OMC特点收集接口成功],状况改变:{} -> {}.", SkuStateEnum.INIT.getDesc(),
                    SkuStateEnum.TASK_DELIVERY.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.DELIVERY_FAIL)
            .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL)
            .action(ctx -> {
                log.info("[调用OMC特点收集接口失利],状况改变:{} -> {}.", SkuStateEnum.INIT.getDesc(),
                    SkuStateEnum.DELIVERY_FAIL.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.CONCAT_SUPPLIER)
            .event(SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK)
            .action(ctx -> {
                log.info("[京东无库存,VMI有库存],状况改变:{} -> {}.", SkuStateEnum.INIT.getDesc(),
                    SkuStateEnum.CONCAT_SUPPLIER.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.INIT).target(SkuStateEnum.SUSPEND)
            .event(SkuAttrEventEnum.NO_JD_STOCK_NO_VMI_STOCK)
            .action(ctx -> {
                log.info("[京东和VMI均无库存],状况改变:{} -> {}.", SkuStateEnum.INIT.getDesc(),
                    SkuStateEnum.SUSPEND.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.TASK_DELIVERY)
            .event(SkuAttrEventEnum.HAS_JD_STOCK)
            .action(ctx -> {
                log.info("[京东有库存],状况改变:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(),
                    SkuStateEnum.TASK_DELIVERY.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.RECHECKING)
            .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS)
            .action(ctx -> {
                log.info("[调用OMC特点收集接口成功],状况改变:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(),
                    SkuStateEnum.RECHECKING.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.SUSPEND).target(SkuStateEnum.CONCAT_SUPPLIER)
            .event(SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK)
            .action(ctx -> {
                log.info("[京东无库存,VMI有库存]:{} -> {}.", SkuStateEnum.SUSPEND.getDesc(),
                    SkuStateEnum.CONCAT_SUPPLIER.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.TASK_DELIVERY).target(SkuStateEnum.RECHECKING)
            .event(SkuAttrEventEnum.INVOKE_OMC_SKU_DELIVERY_API_GATHER_FINISH)
            .action(ctx -> {
                log.info("[调用OMC下发查询接口并现已生成收集单]:{} -> {}.", SkuStateEnum.TASK_DELIVERY.getDesc(),
                    SkuStateEnum.RECHECKING.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.TASK_DELIVERY).target(SkuStateEnum.RECHECKED)
            .event(SkuAttrEventEnum.MQ_OMC_SKU_ATTR_CHANGED)
            .action(ctx -> {
                log.info("[OMC的MQ回来SKU特点已改变]:{} -> {}.", SkuStateEnum.TASK_DELIVERY.getDesc(),
                    SkuStateEnum.RECHECKED.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.DELIVERY_FAIL).target(SkuStateEnum.TASK_DELIVERY)
            .event(SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS)
            .action(ctx -> {
                log.info("[调用OMC特点收集接口成功]:{} -> {}.", SkuStateEnum.DELIVERY_FAIL.getDesc(),
                    SkuStateEnum.TASK_DELIVERY.getDesc());
            })
            .and()
            .withExternal().source(SkuStateEnum.CONCAT_SUPPLIER).target(SkuStateEnum.RECHECKED)
            .event(SkuAttrEventEnum.INVOKE_SKU_ATTR_API_CHANGED)
            .action(ctx -> {
                log.info("[调用产品中台jsf接口,回来SKU特点已改变]:{} -> {}.", SkuStateEnum.CONCAT_SUPPLIER.getDesc(),
                    SkuStateEnum.RECHECKED.getDesc());
            });
    }
    /**
     * 大局监听器
     *
     * @return
     */
    private StateMachineListener<SkuStateEnum, SkuAttrEventEnum> listener() {
        return new StateMachineListenerAdapter<SkuStateEnum, SkuAttrEventEnum>() {
            @Override
            public void transition(Transition<SkuStateEnum, SkuAttrEventEnum> transition) {
                //当状况的搬运在configure办法装备中时,会走到该办法。
                log.info("[{}]状况改变:{} -> {}", transition.getKind().name(),
                    transition.getSource() == null ? "NULL" : ofNullableState(transition.getSource().getId()),
                    transition.getTarget() == null ? "NULL" : ofNullableState(transition.getTarget().getId()));
            }
            @Override
            public void eventNotAccepted(Message<SkuAttrEventEnum> event) {
                //当产生的状况搬运不在configure办法装备中时,会走到该办法,此处打印error日志,便利排查状况搬运问题
                log.error("事情未收到: {}", event);
            }
            private Object ofNullableState(SkuStateEnum s) {
                return Optional.ofNullable(s)
                    .map(SkuStateEnum::getDesc)
                    .orElse(null);
            }
        };
    }
}

4.4 事务逻辑处理

4.4.1 构建状况机

对每个sku的操作,经过状况机工厂stateMachineFactory.getStateMachine

//注入状况机工厂实例
@Autowired
private StateMachineFactory<SkuStateEnum, SkuAttrEventEnum> stateMachineFactory;
//构建状况机
public StateMachine<SkuStateEnum, SkuAttrEventEnum> buildStateMachine(String skuId) throws BusinessException {
        if (StringUtils.isEmpty(skuId)) {
            return null;
        }
        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine = null;
        try {
            //从DB中获取当时skuId对应的状况
            LambdaQueryWrapper<SkuAttrRecheckState> query = Wrappers.lambdaQuery(SkuAttrRecheckState.class);
            query.eq(SkuAttrRecheckState::getSkuId, skuId);
            SkuAttrRecheckState skuAttrRecheckState = this.baseMapper.selectOne(query);
            SkuStateEnum skuStateEnum = SkuStateEnum.getByState(
                skuAttrRecheckState == null ? SkuStateEnum.INIT.getState() : skuAttrRecheckState.getState());
            //从状况机工厂获取一个状况机
            stateMachine = stateMachineFactory.getStateMachine(skuId);
            stateMachine.stop();
            //装备状况机参数
            stateMachine.getStateMachineAccessor().doWithAllRegions(sma -> {
                //装备状况机拦截器,当状况产生搬运时,会走到该拦截器中
                sma.addStateMachineInterceptor(new StateMachineInterceptorAdapter<SkuStateEnum, SkuAttrEventEnum>() {
                    @Override
                    public void preStateChange(State<SkuStateEnum, SkuAttrEventEnum> state,
                        Message<SkuAttrEventEnum> message,
                        Transition<SkuStateEnum, SkuAttrEventEnum> transition,
                        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
                        StateMachine<SkuStateEnum, SkuAttrEventEnum> rootStateMachine) {
                        //获取状况搬运时,对应的SKU详细信息
                        SkuAttrRecheckState result = JSON.parseObject(
                            String.class.cast(message.getHeaders().get(JSON_STR)), SkuAttrRecheckState.class);
                        //更新状况机搬运后的状况(来自于4.3.3中的装备)
                        result.setState(state.getId().getState());
                        //将状况机搬运后的状况写入DB
                        LambdaQueryWrapper<SkuAttrRecheckState> query = Wrappers.lambdaQuery(SkuAttrRecheckState.class);
                        query.eq(SkuAttrRecheckState::getSkuId, result.getSkuId());
                        if (baseMapper.exists(query)) {
                            UpdateWrapper<SkuAttrRecheckState> updateQuery = new UpdateWrapper<>();
                            updateQuery.eq("sku_id",result.getSkuId());
                            log.info("更新状况信息:{}", JSON.toJSONString(result));
                            baseMapper.update(result, updateQuery);
                        } else {
                            log.info("写入状况信息:{}", JSON.toJSONString(result));
                            baseMapper.insert(result);
                        }
                    }
                });
                //将状况机的初始状况装备为DB中的skuId对应状况
                sma.resetStateMachine(new DefaultStateMachineContext<SkuStateEnum, SkuAttrEventEnum>(
                    skuStateEnum, null, null, null));
            });
            //启动状况机
            stateMachine.start();
        } catch (Exception e) {
            log.error("skuId={},构建状况机失利.", skuId, e);
            throw new BusinessException("状况机构建失利", e);
        }
        return stateMachine;
    }

4.4.2 封装事情

public synchronized Boolean sendEvent(StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
        SkuAttrEventEnum skuAttrEventEnum, SkuAttrRecheckState skuAttrRecheckState) throws BusinessException {
        try {
            //发送事情,并将需求传递的信息写入header
            stateMachine.sendEvent(MessageBuilder.withPayload(skuAttrEventEnum)
                .setHeader(SKU_ID, skuAttrRecheckState.getSkuId())
                .setHeader(STATE, skuAttrRecheckState.getState())
                .setHeader(JSON_STR, JSON.toJSONString(skuAttrRecheckState))
                .build());
        } catch (Exception e) {
            log.error("发送事情失利", e);
            throw new BusinessException("发送事情失利", e);
        }
        return true;
    }

4.4.3 事务逻辑使用

当用户在界面上对“未操作”状况的SKU点击审阅按钮时,会调用物流OMC接口将SKU特点下发到物流侧,当下发成功时,状况会转化为“使命下发中”,当调用接口失利,则会将状况转化为”下发失利”,中心代码如下:

public Boolean recheck(List<String> skuIds) throws BusinessException {
        if (CollectionUtils.isEmpty(skuIds)) {
            log.error("参数过错,sku列表为空");
            throw new BusinessException("参数过错,sku列表为空");
        }
        List<SkuAttrExceptionDetail> skuDetails = skuAttrExceptionDetailMapper.queryBySkuIdList(skuIds);
        if (CollectionUtils.isEmpty(skuDetails)) {
            log.error("查询sku异常明细结果集为空,skuIds={}", JSON.toJSONString(skuIds));
            return false;
        }
        for (SkuAttrExceptionDetail detail : skuDetails) {
            if (detail.getState() != SkuStateEnum.INIT.getState()) {
                log.info("{}不是未操作状况sku不进行复核", detail.getSkuId());
                continue;
            }
            //构建SKU对应的状况机
            StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine = buildStateMachine(detail.getSkuId());
            SkuAttrRecheckState skuAttrRecheckState = DomainBuilderUtil.buildSkuAttrRecheckState(detail);
            //断定库存并发送事情
            adjustAndSendEvents(detail, stateMachine, skuAttrRecheckState);
        }
        return true;
    }
public void adjustAndSendEvents(SkuAttrExceptionDetail detail,
        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
        SkuAttrRecheckState skuAttrRecheckState) throws BusinessException {
        //1、京东有库存,调用物流特点接口,下发SKU特点
        if (detail.getSpotInventoryQtty() > 0) {
            invokeOmcSkuAttrCollectApiAndSendEvent(detail, stateMachine, skuAttrRecheckState);
            return;
        }
        //2、京东无库存,有VMI库存
        if (detail.getSpotInventoryQtty() <= 0 && detail.getVmiInventoryQtty() > 0) {
            sendEvent(stateMachine, SkuAttrEventEnum.NO_JD_STOCK_HAS_VMI_STOCK, skuAttrRecheckState);
            return;
        }
        //3、京东和VMI均无库存
        if (detail.getSpotInventoryQtty() <= 0 && detail.getVmiInventoryQtty() <= 0) {
            sendEvent(stateMachine, SkuAttrEventEnum.NO_JD_STOCK_NO_VMI_STOCK, skuAttrRecheckState);
            return;
        }
    }
private void invokeOmcSkuAttrCollectApiAndSendEvent(SkuAttrExceptionDetail detail,
        StateMachine<SkuStateEnum, SkuAttrEventEnum> stateMachine,
        SkuAttrRecheckState skuAttrRecheckState) throws BusinessException {
        DistrustAttributeGatherRequest request = RequestUtil.buildOmcAttrCollectRequest(detail, reqSource);
        try {
            if (jsfInvokeService.invokeSkuAttrCollectApi(request)) {
                sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_SUCCESS,
                    skuAttrRecheckState);
            } else {
                sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL, skuAttrRecheckState);
            }
        } catch (Exception e) {
            log.error("调用物流Sku特点收集接口过错,request={}", JSON.toJSONString(request), e);
            sendEvent(stateMachine, SkuAttrEventEnum.INVOKE_OMC_ATTR_COLLECT_API_FAIL, skuAttrRecheckState);
        }
    }

五、总结

本文经过介绍有限状况机,并结合详细项目,经过状况机的使用将状况和事务逻辑解耦,便于简化杂乱事务逻辑,下降了解本钱。另外,状况机的使用场景关于那种杂乱的流程也相同合适,能够在实践项目中根据状况机中心要素梳理出隐性的状况转化联络,从而将一个杂乱的流程问题转化为状况机的模式问题,再利用状况机模式来完成,能够有助于我们优雅的处理更广泛的杂乱事务问题。