作者:京东批发 樊思国
一、背景在研发我的项目中,常常能遇到简单的状态流转类的业务场景,比方游戏编程中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
...