我在項(xiàng)目中這樣使用狀態(tài)機(jī)

最近一個(gè)新的項(xiàng)目中的一個(gè)業(yè)務(wù),狀態(tài)的流轉(zhuǎn)比較復(fù)雜,涉及到二十幾個(gè)狀態(tài)的流轉(zhuǎn),而且吸取了其他業(yè)務(wù)教訓(xùn),我們決定使用狀態(tài)機(jī)來解決狀態(tài)流轉(zhuǎn)的問題。

要使用狀態(tài)機(jī)除了自己寫狀態(tài)模式下還研究了當(dāng)下兩個(gè)開源項(xiàng)目,一個(gè)是spring的state machine,一個(gè)是cola-state-machine。

spring的狀態(tài)機(jī)可以做狀態(tài)持久化,和spring結(jié)合比較好,但是太重了。 cola就比較簡單,它只是簡單做了一個(gè)抽象,我們只需要實(shí)現(xiàn)具體的行為就行了。 使用cola最重要的就是要記得"因?yàn)槟硞€(gè)事件,導(dǎo)致了狀態(tài)A向狀態(tài)B進(jìn)行了遷移",當(dāng)然這里的狀態(tài)可以是同一個(gè)。

因?yàn)轫?xiàng)目中使用的是springboot,所以我這里結(jié)合起來做了一定的改造,下面給出我在項(xiàng)目中使用的例子,僅供大家參考

引入依賴

<dependency>
    <groupId>com.alibaba.cola</groupId>
    <artifactId>cola-component-statemachine</artifactId>
    <version>4.3.2</version>
</dependency>

定義

因?yàn)槟硞€(gè)事件,導(dǎo)致了狀態(tài)A向狀態(tài)B進(jìn)行了遷移。所以需要定義狀態(tài),事件,流程。

state-process.png

事件

根據(jù)我們的流程我定義了以下事件

@AllArgsConstructor
@Getter
public enum StatusChangeEventEnum {
    // save as draft
    SAVE_AS_DRAFT_EVENT,
    // draft submit
    DRAFT_SUBMIT_EVENT,
    // submit
    SUBMIT_EVENT;
}

狀態(tài)

@Getter
@AllArgsConstructor
public enum StatusEnum {

    NONE(0, "None"),
    DRAFT(1, "Draft"),
    SUBMITTED(2, "Submitted");

    private Integer code;
    private String desc;
}

流程

定義狀態(tài)遷移和事件的關(guān)系

@Getter
@AllArgsConstructor
public enum StatusChangeEnum implements StatusChange {

    SAVE_AS_DRAFT(NONE, DRAFT, SAVE_AS_DRAFT_EVENT),
    SUBMIT(NONE, SUBMITTED, SUBMIT_EVENT),
    DRAFT_SUBMIT(DRAFT, SUBMITTED, DRAFT_SUBMIT_EVENT);

    private StatusEnum fromStatus;
    private StatusEnum toStatus;
    private StatusChangeEventEnum event;

    @Override
    public StatusEnum from() {
        return fromStatus;
    }

    @Override
    public StatusEnum to() {
        return toStatus;
    }

    @Override
    public StatusChangeEventEnum event() {
        return event;
    }

}

// 抽象狀態(tài)變更的接口,因?yàn)榭赡軙?huì)存在多個(gè)不同的狀態(tài)變更流程
public interface StatusChange {

    StatusEnum from();

    StatusEnum to();

    StatusChangeEventEnum event();

}

使用Spring管理狀態(tài)機(jī)


@Configuration
@RequiredArgsConstructor
public class StateMachineConfiguration {
    private final List<StateMachineHandler> handlerList;
    private static final String NEW_REQUEST_STATE_MACHINE = "newRequestStateMachine";

    @Bean(NEW_REQUEST_STATE_MACHINE)
    public <C> StateMachine<StatusEnum, StatusChangeEventEnum, C> newRequestStateMachine() {

        StateMachineBuilder<StatusEnum, StatusChangeEventEnum, C> builder = StateMachineBuilderFactory.create();

        for (StatusChangeEnum changeEnum :StatusChangeEnum.values()) {
            build(builder, changeEnum);
        }
        return builder.build(NEW_REQUEST_STATE_MACHINE);
    }

    private <C> void build(StateMachineBuilder<StatusEnum, StatusChangeEventEnum, C> builder, StatusChange statusChange) {
        // 找到對(duì)應(yīng)的handler來處理
        StateMachineHandler<StatusEnum, StatusChangeEventEnum, C> handler = getHandler(statusChange);

        StatusEnum fromStatus = statusChange.from();
        StatusEnum toStatus = statusChange.to();
        StatusChangeEventEnum changeEvent = statusChange.event();
        // 只產(chǎn)生了事件,但是狀態(tài)未發(fā)生變化
        if (fromStatus == toStatus) {
            builder.internalTransition()
                    .within(fromStatus)
                    .on(changeEvent)
                    .when(handler::isSatisfied)
                    .perform((from, to, event, ctx) -> handler.execute(from, to, event, ctx));
        } else {
            builder.externalTransition()
                    .from(fromStatus)
                    .to(toStatus)
                    .on(changeEvent)
                    .when(handler::isSatisfied)
                    .perform((from, to, event, ctx) -> handler.execute(from, to, event, ctx));
        }
        // 直接在handler中拋出更詳細(xì)異常
        //builder.setFailCallback(new AlertFailCallback<>());
    }

    private <C> StateMachineHandler<StatusEnum, StatusChangeEventEnum, C> getHandler(StatusChange statusChange) {
        return handlerList.stream().filter(handler -> handler.canHandle(statusChange))
                .findFirst()
                .orElse(new DefaultStateMachineHandler<C>());
    }

}

經(jīng)過上面的定義后,后續(xù)有新的狀態(tài)變更流程,我們只需要在 StatusChangeEnum 中添加就行了。

實(shí)現(xiàn)對(duì)應(yīng)的handler

這里我舉一個(gè)例子,比如說首次提交數(shù)據(jù)


@Component
@RequiredArgsConstructor
public class NoneToSubmittedStatusHandler  implements 
                        StateMachineHandler<StatusEnum, StatusChangeEventEnum, SubmitDTO> {
    @Override
    public boolean canHandle(StatusChange statusChange) {
        // handler能處理的變更流程
        StatusChangeEnum changeEnum = StatusChangeEnum.SUBMIT;
        return statusChange == changeEnum;
    }

    @Override
    public void execute(StatusEnum from, StatusEnum to, StatusChangeEventEnum event, SubmitDTO context) {
        // 執(zhí)行具體的業(yè)務(wù)邏輯,比如插入數(shù)據(jù)
    }

    @Override
    public boolean isSatisfied(SubmitDTO context) {
        // 判斷是否滿足條件,比如是否是對(duì)應(yīng)的用戶等
        return true;
    }
}

這樣子我們的每一個(gè)handler的功能就比較專一了,只需要處理對(duì)應(yīng)狀態(tài)的就行了,你可能回想要是有些狀態(tài)的變成要做的事情類似,這樣的代碼不可能寫兩遍吧? 其實(shí)我們可以有一個(gè)抽象類可以將這些公用的邏輯放到抽象類里面,這樣子有相同邏輯的就可以使用了。

使用

萬事具備,現(xiàn)在只差在項(xiàng)目中使用了

@Component
@RequiredArgsConstructor
public class StateMachine {

 private final StateMachine<StatusEnum, StatusChangeEventEnum, StatusChangeContext> newRequestStateMachine;


    @Transactional(rollbackFor = Exception.class)
    public void statusChange(StatusChange changeEnum, StatusChangeContext context) {
        newRequestStateMachine.fireEvent(changeEnum.from(), changeEnum.event(), context);
    }

}

public abstract class StatusChangeContext {
    
}

@Data
public class SubmitDTO extends StatusChangeContext {

    private Long id;

    private Long String;
}

只要涉及到狀態(tài)變更的,就都可以調(diào)用StateMachie了。

寫到最后

這種方式其實(shí)會(huì)導(dǎo)致類的數(shù)量變多,但是職責(zé)更加清晰,每個(gè)類的代碼行數(shù)也并不多,而且以后想要找某個(gè)狀態(tài)變更到某個(gè)狀態(tài)做了什么時(shí)候很很好找。

這就是最近使用狀態(tài)機(jī)的一些心得,希望能對(duì)你有所幫助。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容