squirrel-foundation狀態機的使用細節

squirrel-foundation狀態機的使用細節

date: 2017-06-19 15:50:18java

上一篇文章介紹了stateless4j、spring-statemachine以及squirrel-foundation三款狀態機引擎的實現原理,以及我爲什麼選擇squirrel-foundation做爲解決方案。本文主要介紹一下項目中如何使用squirrel-foundation的一些細節以及如何與spring進行集成。在閱讀本文前,建議先閱讀官方的使用手冊git

生命週期

狀態機建立過程

  • StateMachine: StateMachine實例由StateMachineBuilder建立不被共享,對於使用annotation方式(或fluent api)定義的StateMachine,StateMachine實例即根據此定義建立,相應的action也由本實例執行,與spring的集成最終要的就是講spring的bean實例注入給由builder建立的狀態機實例;github

  • StateMachineBuilder: 本質上是由StateMachineBuilderFactory建立的動態代理。被代理的StateMachineBuilder默認實現爲StateMachineBuilderImpl,內部描述了狀態機實例建立細節包括State、Event、Context類型信息、constructor等,同時也包含了StateMachine的一些全局共享資源包括StateConverter、EventConverter、MvelScriptManager等。StateMachineBuilder可被複用,使用中可被實現爲singleton;spring

  • StateMachineBuilderFactory: 爲StateMachineBuilder建立的動態代理實例;編程

事件處理過程

  • 狀態正常遷移
    TransitionBegin--(exit->transition->entry)-->TransitionComplete-->TransitionEndapi

  • 狀態遷移異常
    TransitionBegin--(exit->transition->entry)-->TransitionException-->TransitionEndapp

  • 狀態遷移事件拒絕
    TransitionBegin-->TransitionDeclined-->TransitionEndless

statemachine lifecycle

spring集成

從statemachine的生命流程上能夠看到,StateMachineBuilder能夠單例方式由spring container管理,而stateMachine的instance的生命週期伴隨着請求(或業務)。
從這兩點出發,集成spring須要完成兩件事:分佈式

  • (1).經過Spring建立StateMachineBuilder實例;ide

  • (2).業務函數中經過(1)的StateMachineBuilder實例建立StateMachine實例,並向StateMachine暴露SpringApplicationContext;

泛型參數+覆蓋默認構造函數隱藏StateMachineBuilder建立細節,實現ApplicationContextAware接口,接受applicationContext注入,並注入給stateMachine實例。

public abstract class AbstractStateMachineEngine<T extends UntypedStateMachine> implements ApplicationContextAware {
    protected UntypedStateMachineBuilder stateMachineBuilder = null;
    @SuppressWarnings("unchecked")
    public AbstractStateMachineEngine() {
        //識別泛型參數
        Class<T> genericType = (Class<T>)GenericTypeResolver.resolveTypeArgument(getClass(),
            AbstractStateMachineEngine.class);
        stateMachineBuilder = StateMachineBuilderFactory.create(genericType, ApplicationContext.class);
    }
    //注入applicationContext,並在建立StateMachine實例時注入
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    //delegate fire
    public void fire(int rmaId, State initialState, Trigger trigger, StateMachineContext context) {
        T stateMachine = stateMachineBuilder.newUntypedStateMachine(
                            initialState
                            //暫時開啓debug進行日誌trace
                            StateMachineConfiguration.create().enableDebugMode(true).enableAutoStart(true),
                            //注入applicationContext
                            applicationContext);
        stateMachine.fire(trigger, context);
    }
    ...
}
@Service
class DiscountRefundStateMachineEngine extends AbstractStateMachineEngine<DiscountRefundStateMachine> {
}
@Service
public class ReturnGoodsStateMachineEngine extends AbstractStateMachineEngine<ReturnGoodsStateMachine> {
}

StateMachine定義,接受SpringContext注入

@StateMachineParameters(stateType = State.class, eventType = Trigger.class,
    //StateMachineContext 自定義上下文,用來傳遞數據
    contextType = StateMachineContext.class)
@States({
    @State(name = "PENDING", initialState = true),
    @State(name = "CONFIRMING"),
    @State(name = "REJECTED"),
    @State(name = "REFUND_APPROVING"),
    @State(name = "REFUND_APPROVED"),
    @State(name = "REFUND_FINISHED")
})
@Transitions({
    @Transit(from = "PENDING", to = "CONFIRMING", on = "APPLY_CONFIRM",
        callMethod = "doSomething"),
    @Transit(from = "CONFIRMING", to = "REJECTED", on = "REJECT"),
    @Transit(from = "CONFIRMING", to = "REFUND_APPROVING", on = "APPLY_APPROVED"),
    @Transit(from = "REFUND_APPROVING", to = "REFUND_APPROVED", on = "REFUND_APPROVED"),
    @Transit(from = "REFUND_APPROVED", to = "REFUND_FINISHED", on = "REFUND_FINISH_CONFIRM")
})
public class DiscountRefundStateMachine extends AbstractUntypedStateMachine {
    protected ApplicationContext applicationContext;
    //定義構造函數接受ApplicationContext注入([參看New State Machine Instance](http://hekailiang.github.io/squirrel/))
    public DiscountRefundStateMachine(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }
    public void doSomething(State fromState, State toState, Trigger event,
                         StateMachineContext stateMachineContext) {
         DemoBean demoBean = this.applicationContext.get("demoBean");
         //do something
    }
    ...
}

狀態持久化

從StateMachine的事件響應流程中能夠看到,TransitionBegin--(exit->transition->entry)-->TransitionComplete-->TransitionEnd,在TransitionComplete發生一個狀態已從source遷移到了target狀態,因此我選擇了在這個時間點進行了狀態的持久化(沒有選擇TransitionEnd作持久化,由於某些場景在持久化完成後還會存在一些外部動做的觸發,例如通知第三方系統當前狀態已完成變動)。

public class DiscountRefundStateMachine extends AbstractUntypedStateMachine {
    ..
    @Override
    protected void afterTransitionCompleted(Object fromState, Object toState, Object event, Object context) {
        if (context instanceof StateMachineContext && toState instanceof State) {
            StateMachineContext stateMachineContext = (StateMachineContext)context;
            //從上下文中獲取須要持久化的數據,例如訂單ID等
            Rma rma = stateMachineContext.get(MessageKeyEnum.RMA);
            //持久化
            rma.setStatus((State)toState);
            this.applicationContext.get("rmaRepository").updateRma(rma);
        } else {
            throw new Exception("type not support, context expect " + StateMachineContext.class.getSimpleName() + ", actually "
                    + context.getClass().getSimpleName() + ", state expect " + State.class.getSimpleName()
                    + ", actually "
                    + toState.getClass().getSimpleName());
        }
    }
}

分佈式鎖+事務

因爲StateMachine實例不是由Spring容器建立,因此這個過程當中沒法經過註解方式開啓事務(Spring沒有機會去建立事務代理),我採用了編程式事務,在AbstractStateMachineEngine的fire函數中隱式的實現。
AbstractStateMachineEngine#fire

public abstract class AbstractStateMachineEngine<T extends UntypedStateMachine> implements ApplicationContextAware {
    ...
    public void fire(int rmaId, State initialState, Trigger trigger, StateMachineContext context) {
        JedisLock jedisLock = jedisLockFactory.buildLock(rmaId);
        //爭用分佈式鎖
        if (jedisLock.tryLock()) {
            try {
                T stateMachine = stateMachineBuilder.newUntypedStateMachine(
                                    initialState
                                    //暫時開啓debug進行日誌trace
                                    StateMachineConfiguration.create().enableDebugMode(true).enableAutoStart(true),
                                    //注入applicationContext
                                    applicationContext);
                DataSourceTransactionManager transactionManager = applicationContext.get("transactionManager")
                DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
                TransactionStatus status = transactionManager.getTransaction(def);
                try {
                    stateMachine.fire(trigger, context)
                    transactionManager.commit(status);
                } catch (Exception ex) {
                    transactionManager.rollback(status);
                    throw ex;
                }
            } finally {
                jedisLock.release();
            }
        } 
        ...
    }
}

使用graphviz生成狀態拓撲圖

squirrel statemachine提供了DotVisitor、SCXMLVisitor兩種實現方式用於生成狀態機描述文件,項目裏我選擇了graphviz用來作狀態拓撲
graphviz gui工具下載
PS:因爲squirrel默認的DotVisitorImpl對帶中文描述屬性的State/Event枚舉不友好,我在原有代碼上作了一些調整,有相似需求的能夠看這裏

退貨流程

更多文章請訪問個人博客轉載請註明出處

相關文章
相關標籤/搜索