作者:京东批发 樊思国

一、背景

在研发我的项目中,常常能遇到简单的状态流转类的业务场景,比方游戏编程中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@Slf4jpublic 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

//注入状态机工厂实例@Autowiredprivate 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);        }    }

五、总结

本文通过介绍无限状态机,并联合具体我的项目,通过状态机的利用将状态和业务逻辑解耦,便于简化简单业务逻辑,升高了解老本。另外,状态机的利用场景对于那种简单的流程也同样适宜,能够在理论我的项目中依据状态机外围因素梳理出隐性的状态转换关系,从而将一个简单的流程问题转换为状态机的模式问题,再利用状态机模式来实现,能够有助于咱们优雅的解决更宽泛的简单业务问题。