Spring Statement 状态机应用实例

7 篇文章 0 订阅

在业务系统中,通过应用状态机的方式,将所有的状态、事件、动作都抽离出来,对复杂的状态迁移逻辑进行统一管理,来取代冗长的 if else 判断,能使系统中的复杂问题得以解耦,变得直观、方便操作,使系统更加易于维护和管理。

有限状态机的定义及重要概念见有限状态机

Spring Statement 应用实例

Spring Statemachine 旨在提供以下功能:

  • 易于使用的扁平单级状态机,可用于简单案例
  • 分层状态机结构,以简化复杂的状态配置
  • 状态机区域提供更复杂的状态配置
  • 可以使用触发器、转换流程、守卫和动作
  • 类型安全配置适配器
  • 构建器模式,用于在Spring Application上下文之外使用的简易实例化
  • 常用用例的手册
  • 基于Zookeeper的分布式状态机
  • 状态机事件监听器
  • UML Eclipse Papyrus建模
  • 状态机配置的持久化存储
  • Spring IOC集成将bean与状态机相关联

Spring状态机框架的重要概念可参考Spring Statemachine,以下就不重复概念,直接展示实例

State

流程状态

@Getter
@AllArgsConstructor
public enum ProcessState {

    /**
     * 申请状态(100为中间状态,状态机自动流转,不持久化)
     */
    NONE(-1, "无"),
    DATA_SUBMIT(0, "资料审核进行中"),
    DATA_AUDIT(1, "资料审核进行中"),
    DATA_AUDIT_FAIL(2, "资料审核不通过"),
    GUILD_ELECTRONIC_SIGN(3, "公会签约"),
    ANCHOR_ELECTRONIC_SIGN(4, "主播签约"),
    ELECTRONIC_SIGN_FAIL(5, "签约不通过"),
    PROCESS_SUCCESS(6, "流程成功"),
    FINAL_AUDIT_FAIL(7, "终审不通过"),

    DATA_AUDIT_SUCCESS(100, "资料审核通过");

    private final int value;
    private final String detail;

    public static ProcessState get(int value) {
        for (ProcessState s : values()) {
            if (value == s.value) {
                return s;
            }
        }
        return NONE;
    }
}

Event

触发事件

public enum ProcessEvent {
    /**
     * 申请状态 0不通过 1通过 2拒绝
     */
    FAIL(0, "不通过"),
    SUCCESS(1, "通过"),
    REJECT(2, "拒绝");

    private final int value;
    private final String desc;

    public static ProcessEvent get(int value) {
        for (Events s : values()) {
            if (value == s.value) {
                return s;
            }
        }
        return FAIL;
    }
}

Action

行为动作

@Service
public class DataAuditAction implements Action<States, Events> {

    @Override
    public void execute(StateContext<States, Events> context) {
        log.info("DataAuditAction, state:{}, msg:{}", context.getStateMachine().getState().getId(),
                context.getMessage().toString());
        // 传递数据
        MessageHeaders headers = context.getMessage().getHeaders();
        Apply apply = headers.get("apply", Apply.class);
        
        // 结果状态
        States states = context.getTarget().getId();
        
        // 业务逻辑+数据持久化
    }
}

Guard & Choice

在下述状态机中,审核成功后会到审核成功的中间状态,状态机根据withChoice自动流转状态,满足signGuard条件则执行guildAction,否则执行anchorAction

@Service
public class SignGuard implements Guard<ProcessState, ProcessEvent> {

    @Override
    public boolean evaluate(StateContext<ProcessState, ProcessEvent> context) {
        Apply apply = context.getMessage().getHeaders().get("apply", Apply.class);
        return apply.getType() != null && apply.getType() == 0;
    }
}
builder.configureStates()
        .withStates()
        .initial(ProcessState.DATA_SUBMIT)
        .choice(ProcessState.DATA_AUDIT_SUCCESS)
        .states(EnumSet.allOf(ProcessState.class));

builder.configureTransitions()
        // 资料提交
        .withExternal()
        .source(ProcessState.DATA_SUBMIT).target(ProcessState.DATA_AUDIT)
        .event(ProcessEvent.SUCCESS)
        .action(submitAction, errorHandlerAction)
        .and()
        // 审核通过
        .withExternal()
        .source(States.DATA_AUDIT).target(States.DATA_AUDIT_SUCCESS)
        .event(Events.SUCCESS)
        .and()
        .withChoice()
        .source(ProcessState.DATA_AUDIT_SUCCESS)
        .first(ProcessState.GUILD_ELECTRONIC_SIGN, signGuard, guildAction, errorHandlerAction)
        .last(ProcessState.ANCHOR_ELECTRONIC_SIGN, anchorAction, errorHandlerAction);

异常处理

ErrorAction

@Service
public class ErrorHandlerAction implements Action<State, Event> {

    @Override
    public void execute(StateContext<State, Event> context) {
        Exception ex = context.getException();
        Map<Object, Object> var = context.getExtendedState().getVariables();
        var.put("hasError", true);
        var.put("error", ex);
        var.put("msg", ex.getMessage());
    }
}

实际应用

StateMachine构造器
@Component
@EnableStateMachine
public class ProcessStateMachineBuilder {

    private final static String MACHINE_ID = "ProcessMachine";

    @Resource(name = "errorHandlerAction")
    private ErrorHandlerAction errorHandlerAction;

    @Resource(name = "guildAction")
    private GuildAction guildAction;

    @Resource(name = "signGuard")
    private SignGuard signGuard;

    /**
     * 构建状态机
     *
     * @return StateMachine
     * @throws Exception
     */
    public StateMachine<ProcessState, ProcessEvent> build() throws Exception {
        StateMachineBuilder.Builder<ProcessState, ProcessEvent> builder = StateMachineBuilder.builder();

        builder.configureConfiguration()
                .withConfiguration()
                .machineId(MACHINE_ID);

        builder.configureStates()
                .withStates()
                .initial(ProcessState.DATA_SUBMIT)
                .choice(ProcessState.DATA_AUDIT_SUCCESS)
                .states(EnumSet.allOf(ProcessState.class));

        builder.configureTransitions()
                // 资料提交
                .withExternal()
                .source(ProcessState.DATA_SUBMIT).target(ProcessState.DATA_AUDIT)
                .event(ProcessEvent.SUCCESS)
                .action(submitAction, errorHandlerAction)
                .and()
                // 审核通过
                .withExternal()
                .source(ProcessState.DATA_AUDIT).target(States.DATA_AUDIT_SUCCESS)
                .event(ProcessEvent.SUCCESS)
                .and()
                .withChoice()
                .source(ProcessState.DATA_AUDIT_SUCCESS)
                .first(ProcessState.GUILD_ELECTRONIC_SIGN, signGuard, guildAction, errorHandlerAction)
                .last(ProcessState.ANCHOR_ELECTRONIC_SIGN, anchorAction, errorHandlerAction)
                .and()
                // 签约失败
                .withExternal()
                .source(ProcessState.ANCHOR_ELECTRONIC_SIGN)
                .target(ProcessState.ELECTRONIC_SIGN_FAIL)
                .event(ProcessEvent.FAIL)
                .action(auditFailAction, errorHandlerAction);

        return builder.build();
    }
}
根据当前状态获取对应的状态机
@Service
public class StateMachineCommonService {

    private static final Logger logger = LoggerFactory.getLogger(StateMachineCommonService.class);

    @Autowired
    private ProcessStateMachineBuilder builder;

    /**
     * 根据保底考核状态获取对应的状态机
     *
     * @param state 状态 {@link ProcessState}
     * @return StateMachine
     */
    public StateMachine<ProcessState, ProcessEvent> getStateMachineByState(ProcessState state) {
        try {
            StateMachine<ProcessState, ProcessEvent> stateMachine = builder.build();
            stateMachine.getStateMachineAccessor().doWithAllRegions(function -> function.resetStateMachine(new DefaultStateMachineContext<>
                    (state, null, null, null, null, stateMachine.getId())));
            stateMachine.start();
            logger.info("getStateMachineByStateCode, state:{}", stateMachine.getState().getId().toString());
            return stateMachine;
        } catch (Exception e) {
            logger.error("getStateMachineByStateCode error, state:{}", state, e);
            return null;
        }
    }
}

接口调用
@Transactional(rollbackFor = {BusinessException.class, Exception.class})
public void dataAudit(AuditCommReq req) throws Exception {
    Apply apply = applyRepository.queryById(req.getId());

    StateMachine<ProcessState, ProcessEvent> stateMachine = stateMachineCommonService
            .getStateMachineByState(ProcessState.DATA_AUDIT);

    Message<ProcessEvent> message = MessageBuilder.withPayload(ProcessEvent.SUCCESS)
            .setHeader("apply", apply)
            .build();
    stateMachine.sendEvent(message);
    StateMachineUtils.throwError(stateMachine);
}
异常处理
public class StateMachineUtils {

    public static <S, E> boolean throwError(StateMachine<S, E> stateMachine) throws Exception {
        Map<Object, Object> var = stateMachine.getExtendedState().getVariables();
        Boolean flag = (Boolean) var.get(StateMachineConstants.HAS_ERROR);
        if (flag != null && flag) {
            throw (Exception) var.get(StateMachineConstants.ERROR);
        }
        return false;
    }
}
全局异常处理器
@ControllerAdvice
public class GlobalExceptionHandler {
    private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public PageDataResult businessWarn(BusinessException exception) {
        logger.warn("businessWarn error: passport={} msg={}", LocalUserContext.get().getPassport(), exception.getMessage(), exception);
        return PageDataResult.fail(RespCode.BUSINESS_WARN.getCode(), exception.getMessage());
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public PageDataResult commonException(Exception exception) {
        logger.error("commonException error: passport={} msg={}", LocalUserContext.get().getPassport(), exception.getMessage(), exception);
        return PageDataResult.of(RespCode.ERROR);
    }
}

可优化点

1、将实例池化,避免每个请求实例化状态机,节省开销
2、使用持久化状态机StateMachinePersister恢复状态机对象
3、使用唯一id记录每个状态机


参考资料:

  1. Spring Statement
  2. 有限状态机及Spring Statement框架
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值