成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

ToB復雜業務狀態的可復用的解決方案

開發 架構
今天就介紹關于一個業務:狀態變多、條件分支增多、狀態轉換規則、依賴關系變復雜,導致代碼難以維護,代碼可重用性和可擴展性變差這個問題,一套可復用解決方案。

年底了大家都要述職,老板都會問:你這個方案,是否沉淀形成一套可復用解決方案?

今天就介紹關于一個業務:狀態變多、條件分支增多、狀態轉換規則、依賴關系變復雜,導致代碼難以維護,代碼可重用性和可擴展性變差這個問題,一套可復用解決方案。

1.問題背景

產品:來活啦,有用戶反饋我們的APP沒有線上退保證金功能,都在抖音上找到老板反饋了。

產品:我們商戶入駐狀態有:未入駐、待入駐、已入駐、清退中、已清退。清退中又有子狀態 商戶申請關戶中、統計商戶有責貨損中、待商戶確認有責貨損、申訴商戶有責貨損中、提現失敗、提現成功、已清退等。

我這里簡單畫一下關戶狀態流轉圖

圖片圖片

2.如何解決狀態問題

在軟件開發過程中,隨著功能的不斷迭代和添加,代碼的可維護性往往會面臨挑戰。

我們有一個商戶入駐的功能,功能也是由簡單迭代到復雜,現在又要增加商戶退出、重新入駐相關狀態,狀態轉換邏輯也變得復雜,在這個迭代過程中,我們如何防止屎山代碼形成?

2.1 解耦重構優化代碼

項目初期,狀態比較少,我們通過如下方法重構代碼就可以解決問題。

圖片圖片

下面是我們重構優化代碼的例子。

重構前:入駐方法很長,所有業務代碼邏輯耦合在一起。

class A {
  //入駐方法
  public void join() { 
   if(未入駐過){
     // 實人認證
     //查詢關聯賬號
   ...//其他業務邏輯
   }else if(曾入駐過){
   
    //查詢關聯賬號
   //計算售后率
   ...//其他業務邏輯
   }
}

重構后:提取出實人認證、計算售后率等方法。

class A {
   //入駐方法
   public void join() { 
      if(未入駐過){
       // 實人認證
      realPersonAuthentication();
       ...//其他業務邏輯
      }else if(曾入駐過){
       //計算售后率
       calculatingAfterSalesRate();
       ...//其他業務邏輯
      }
      //查詢關聯賬號
      associatedAccount();
    }
    public void realPersonAuthentication() { 
    // 實人認證
    }
   
    public void associatedAccount() { 
    //查詢關聯賬號
    }
    public void calculatingAfterSalesRate() {
    //計算售后率
    }
}

2.2 使用設計模式重構優化

使用狀態模式、策略模式等都能幫助我們的代碼清晰易讀。

下面就是個狀態模式解決不同入駐狀態下商戶擁有不同權限的例子,如果你的業務場景有如下特點,那么可以嘗試使用狀態模式優化。

1)對象行為取決于狀態。

比如商戶是否可以送檢行為取決于商戶的已入駐狀態。

2)狀態轉換邏輯復雜。

比如商戶申請關戶中、待確認貨損狀態才能撤銷關戶等。

3)避免大量條件語句。

這個業務場景,如果使用ifelse判斷,需要寫6個,代碼也不好擴展維護。

業務規則:

圖片圖片

狀態模式類圖:

狀態模式類圖狀態模式類圖

2.3 使用狀態機

如果你的狀態很多,并且轉換邏輯很復雜的時候,我們可以使用狀態機,幫助我們進行狀態管理。

2.3.1 思考狀態機特點

看上面狀態圖,思考我們開發的其他場景的狀態流轉圖,這些場景是不是有以下共同特點:1)根據不同觸發條件執行不同處理動作最后轉變為新的狀態(狀態也有可能不變)。

2)狀態間轉換邏輯比較復雜。如果使用ifelse進行判斷,業務復雜之后很難維護。

3)各個狀態轉換分散在不同方法中,很難清晰知道狀態全部的流轉規則。

2.3.2 總結歸納狀態機特點

針對狀態流轉過程,其實是有很多相似的地方,我們可以簡單總結歸納一下。

1)我們可以考慮將狀態全部從業務層抽離,統一進行收口維護。

2)抽象狀態流轉規則, 比如:已入駐 狀態轉變為 清退中 狀態,需要原狀態是已入駐,并要完成一些業務操作(記錄申請信息,通知商戶、BD等等)。

3)我們將狀態流轉規則也進行統一維護(配置文件配置、代碼里寫死配置、數據庫里配置等),這樣便能清晰看到全部流轉規則,簡化狀態管理。

3.狀態機介紹

狀態機其實很簡單,不然也不會這么流行。由上面抽象總結出來的方法,其實就是狀態機。狀態機的概念很早就提出了,而且比Java語言本身還要早。(詳見百度百科)

3.1 狀態機的四大概念

狀態機核心概念狀態機核心概念

第一個是State,狀態。一個狀態機至少要包含兩個狀態。例如上面例子,有 未入駐、已入駐、清退中、已清退等多個狀態。

第二個是Event,事件。事件就是執行某個操作的觸發條件。對于關戶流程,”申請關戶” 就是一個事件。

第三個是Action,動作。事件發生以后要執行的動作。例如事件是“申請關戶”,動作是“記錄申請信息、通知BD聯系商戶等”。編程的時候,一個Action一般就對應一個函數或者類。

第四個是Transition,描述了一個狀態機如何從一個狀態轉換到另一個狀態相關規則、條件。當特定的 Event事件發生時,狀態機會根據Transition規則執行狀態轉換。這個過程中,可能伴隨著動作(Action)的執行,這些動作可以是狀態進入前執行的預處理、狀態轉換時執行的中間操作或狀態退出后執行的清理工作。例如 “商戶操作入駐,狀態從未入駐轉到已入駐” 就是一個變換。

3.2 狀態機的優點

參考網上各種描述,這里就簡單提一下

1)清晰的狀態管理:其實就是統一在一個地方配置轉態流轉規則,這有助于確保系統的行為符合預期(可以在配置文件配置,代碼寫死,數據庫配置流轉規則),減少錯誤和不確定性。

2)易于維護和擴展:狀態機的結構使得對系統的修改和擴展變得更加容易。由于狀態機的各個部分相對獨立,因此可以在不干擾其他部分的情況下添加新狀態或修改現有狀態

3)促進團隊溝通:比如前面的狀態流轉圖,狀態機提供了一種圖形化的方式來描述系統行為,這對于團隊成員之間的溝通非常有幫助。非技術團隊成員也能理解狀態圖,從而更好地參與項目討論

4.狀態機對比與選型

下面我們就介紹幾種狀態機的核心實現,大家可以作為參考,搭建符合自己業務場景的狀態機。

4.1 轉轉狀態機

轉轉狀態機主要看這個圖。

轉轉狀態機流程圖轉轉狀態機流程圖

1)項目啟動,從Mysql數據庫讀取狀態機配置的狀態轉換規則Transition,并在內存使用Map保存。

2)Event事件發生后,根據當前狀態、事件到Map獲取對應的Action執行。

3)這是init()方法加載轉換規則,轉換為Map,Map<String, List> fsmNodeMap,key是事件+當前狀態,value是要執行的FsmNode類。

public class FsmNode {
    private Integer opType;
    private Integer role;
    private Integer sourceStatus;
    private Integer targetStatus;
    private NodeType nodeType;
    private FsmAction action;
    private HashSet<JobConfig> jobConfigs;
    private HashSet<TransactionConfig> transactionConfigs;
}

總結起來有如下特點

1)把狀態機流轉條件、執行的業務類名(Transition)配置到數據庫。如果你的狀態機、狀態轉換條件特別多,那么存儲在數據庫就很方便查找。

2)通過定時任務輪訓數據表,重試執行失敗的Action。

3)無狀態設計,每次狀態流轉無需生成狀態機實例,只要根據當前狀態、事件到Map里找到需要執行的Action及job來執行就好。

4)支持每次狀態轉換的時候發送事務消息(結合數據庫事務實現)適用場景:狀態機轉換條件配置特別多,需要定時任務補償執行,需要在狀態轉換時候發送事務消息的場景。

以上就是宙斯狀態機的核心功能,總結來說就是:把事件、當前狀態、目標狀態、要執行的Action類配置數據庫里,執行時候根據這個配置來查找Action并執行。

4.2 Cola狀態機

參考Cola的官方文檔。阿里團隊覺得開源狀態機不好用,太復雜且性能差,就自己寫了一個簡潔版狀態機。詳見博客

Cola狀態機是一個輕量級的開源框架,相比Spring Statemachine和squirrel-foundation,它更加簡單、輕量且性能極高。

Cola狀態機主要有如下特點:

1)簡單、輕量,僅支持狀態流轉的狀態機,不支持嵌套、并行等高級玩法。

2)無狀態設計。可以使用一個狀態機實例來響應所有的請求。

適用場景:Cola狀態機適用于需要高并發支持且希望簡化狀態管理的場景。

4.2.1 Cola狀態機核心源碼解析

Cola 狀態機本質上是狀態模式+兩個Map。

核心類核心類

第一個Map,Map<S, State< S, E, C>> stateMap,key為當前狀態,value為State對象。

State對象類似狀態模式,內部存儲了狀態轉移transitions map,即第二個Map。

第二個Map,HashMap<E, Transition<S, E,C>> transitions,在State對象內部,key是Event事件,value是Transition對象,Transition內有要執行的Action、初始狀態、目標狀態、驅動事件(Event)等。

執行過程

1)業務代碼執行入口,根據當前狀態、事件傳參執行。

StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID+"1");
        //觸發一次事件代碼
        States target = stateMachine.fireEvent(States.STATE2, Events.EVENT1, new Context());

2)根據當前狀態找到map里的State。

public S fireEvent(S sourceStateId, E event, C ctx){
        isReady();
        //找到當前狀態對應的State
        State sourceState = getState(sourceStateId);
        //執行
        return doTransition(sourceState, event, ctx).getId();
    }

3)從State里找到另一個Map,transitions,找到map配置的value,轉換規則。

private State<S, E, C> doTransition(State sourceState, E event, C ctx) {
        //找到對應的Transition
        Optional<Transition<S,E,C>> transition = sourceState.getTransition(event);
        if(transition.isPresent()){
          //執行
            return transition.get().transit(ctx);
        }
        Debugger.debug("There is no Transition for " + event);
        return sourceState;
    }

4)找到Transition里的action執行。

@Override
    public State<S, E, C> transit(C ctx) {
        Debugger.debug("Do transition: "+this);
        this.verify();
        if(condition == null || condition.isSatisfied(ctx)){
            if(action != null){
            //執行具體的action
                action.execute(source.getId(), target.getId(), event, ctx);
            }
            return target;
        }

        Debugger.debug("Condition is not satisfied, stay at the "+source+" state ");
        return source;
    }

Cola的源碼很簡單,就是把當前狀態、觸發的事件、目標狀態、執行的業務Action都配置兩個Map里,執行時候根據當前狀態,事件分別去兩個map里拿Action出來執行就好了。

4.3 Spring Statemachine

參考Spring Statemachine的官方文檔。Spring官方提供的一個狀態機框架,支持狀態的嵌套(substate)、狀態的并行(parallel, fork, join)、子狀態機等高級特性。

1)簡單易用

2)狀態機結構層次化,有助于簡化狀態控制的開發過程。

3、功能完備。與Cola狀態機類似,也包括狀態(State)、事件(Event)、轉換(Transition)、動作(Action)等要素。并提供了更豐富的狀態類型配置,如choice、join、fork、history等。

適用場景:適用于需要將復雜邏輯拆分為較小可管理任務、狀態嵌套或需要循環遍歷if-else結構并進行異常處理的場景。

4.3.1 部分源碼解析

1)我們看看狀態轉換時,sendEvent都進行了哪些操作。

Region.sendEvent() 向狀態機發布事件。

private Flux<StateMachineEventResult<S, E>> handleEvent(Message<E> message) {
  
  //檢查狀態機錯誤 
//如果狀態機存在錯誤(通過hasStateMachineError()方法檢查),則立即返回一個包含單個StateMachineEventResult對象的Flux流。
  //這個對象表示事件被拒絕(ResultType.DENIED),并包含了當前狀態機實例、原始消息和拒絕結果類型。
  if (hasStateMachineError()) {
   return Flux.just(StateMachineEventResult.<S, E>from(this, message, ResultType.DENIED));
  }
  //處理正常事件流:
  return Mono.just(message)
   .map(m -> getStateMachineInterceptors().preEvent(m, this))
   .flatMapMany(m -> acceptEvent(m))
   .onErrorResume(error -> Flux.just(StateMachineEventResult.<S, E>from(this, message, ResultType.DENIED)))
   .doOnNext(notifyOnDenied());
 }

AbstractStateMachine.acceptEvent()接收事件。

//發送事件并收集結果:
    return cs.sendEvent(message)
     .collectList()
  //處理發送事件的結果:
使用collectList().flatMapMany(l -> {...})來收集sendEvent的結果,并將它們轉換為一個新的Flux流。
     .flatMapMany(l -> {
      Flux<StateMachineEventResult<S, E>> ret = Flux.fromIterable(l);
      if (!l.stream().anyMatch(er -> er.getResultType() == ResultType.ACCEPTED)) {
       Mono<StateMachineEventResult<S, E>> result = Flux.fromIterable(transitions)
        .filter(transition -> cs != null && transition.getTrigger() != null)
        .filter(transition -> StateMachineUtils.containsAtleastOne(transition.getSource().getIds(), cs.getIds()))
        .flatMap(transition -> {
  //查找可接受的觸發條件:
遍歷狀態轉換(transitions),找到與當前狀態和事件負載匹配的觸發條件。
對每個匹配的觸發條件,評估其是否應該觸發(通過transition.getTrigger().evaluate(triggerContext))。
如果觸發條件評估為真,則使用stateMachineExecutor.queueEvent將事件加入隊列,并設置一個回調來處理執行結果。
  
         return Mono.from(transition.getTrigger().evaluate(triggerContext))
          .flatMap(
  ...
      
      return ret;

StateMachineExecutor.queueEvent()將事件添加到狀態機的隊列。

public Mono<Void> queueEvent(Mono<Message<E>> message, StateMachineExecutorCallback callback) {
  //這行代碼將傳入的事件消息(message)和一個延遲事件列表(deferList)合并成一個單一的Flux<Message<E>>流。這意味著所有這些事件都將按順序被處理。
  Flux<Message<E>> messages = Flux.merge(message, Flux.fromIterable(deferList));

  MonoSinkStateMachineExecutorCallback triggerCallback = new MonoSinkStateMachineExecutorCallback();
  Mono<Void> triggerCallbackSink = Mono.create(triggerCallback);

  return messages
   .flatMap(m -> handleEvent(m, callback, triggerCallback))
   .flatMap(tqi -> Mono.fromRunnable(() -> {
     triggerSink.emitNext(tqi, EmitFailureHandler.FAIL_FAST);
    })
    .retryWhen(Retry.fixedDelay(10, Duration.ofMillis(10))))
   .then()
   .and(triggerCallbackSink);
 }

從上面的源碼我們大概可以看出 Spring狀態機內部通過事件發布、訂閱和線程池、阻塞隊列實現了整個狀態的流轉。到這里介紹完畢Spring狀態機,詳細使用建議參考官方文檔,里面有很多高級用法,這里只是進行簡單功能介紹。

5.選型總結

特性

Cola狀態機

Spring狀態機

轉轉狀態機

集成性

較為獨立,但易于集成到其他Java項目中

與Spring框架緊密集成,方便使用Spring的各種特性(如依賴注入、AOP等)

依賴數據庫,易于集成到Java項目

功能豐富性

支持基本的狀態定義和轉換

提供了完整的狀態機功能,包括狀態定義、事件觸發、動作執行、狀態轉換和守衛條件等,并發狀態、子狀態機

支持基本的狀態定義和轉換,同時提供失敗重試、事務消息等功能

學習成本

低,源碼較簡單,同步API,使用更為直觀

較高,使用Reactive的Mono、Flux等響應式編程模型

較低,源碼較簡單易理解

使用場景

適用于中小型項目、需要輕量級狀態機管理的場景

適用于需要管理復雜狀態轉換邏輯、與Spring框架緊密結合的業務場景

需要輕量級狀態機管理,狀態機配置較多場景

5.1 業務簡單用Cola狀態機

從前面的源碼分析來看,Cola狀態機通過兩個map來實現狀態流轉,源碼簡單易懂,擴展也方便。更加簡潔和直觀的狀態管理方式。如果你的項目比較簡單,開發人員也比較少,僅使用狀態機輔助狀態流轉,在滿足業務需求的前提下,可以考慮使用Cola狀態機。

5.2 業務復雜用Spring Statemachine

Spring Statemachine源碼、功能相對復雜,使用起來不夠直觀;同時不是無狀態的,因此在線程安全性方面需要額外的處理,但是功能完備,提供了豐富的狀態和事件處理機制,與Spring框架緊密集成,可以利用Spring的依賴注入、面向切面編程等特性,方便地進行擴展和集成。如果你的狀態轉換比較復雜,涉及到層次化狀態、并發狀態、子狀態機,同時研發人員較多,研發實力較強,后續考慮擴展功能,可以考慮使用Spring的狀態機。

5.3 個性化訴求自研

如果你考慮自研狀態機,并且你的狀態轉換比較多,或者轉換后,Action有重試需求,也可以參考轉轉宙斯狀態機,將狀態轉換邏輯配置到數據,方便查找狀態轉換配置及從數據庫獲取Action數據進行重試處理。

5.4 B2B復雜業務場景選型

下面是我把B2B整個保賣業務簡化之后的流程圖,大家可以看到整個鏈路流程是很長很復雜。

保賣業務流程簡圖保賣業務流程簡圖

分而治之。我們把長鏈路劃分多為個階段,每個階段有自己的狀態,使用狀態機來管理每個階段狀態流轉,這樣就能把復雜問題簡化為每個小問題解決。

回收段:

回收段狀態圖回收段狀態圖


銷售段:

圖片圖片

售后段:

圖片圖片

篇幅有限,就不列出詳細代碼了。

我們在保賣業務、關戶退保證金項目中使用Spring狀態機,主要考慮是我們業務場景特別復雜。同時,我們根據保賣業務特點,在Spring狀態機基礎功能上進行一些擴展。

調研發現B2B行業友商也使用了Spring狀態機,他們考慮點也是狀態機對于復雜的業務場景的支持。

1)性能方面,我們B2B業務對并發性能要求不高,但是業務復雜度高,Spring狀態機性能完全可以滿足我們需求。

2)功能方面,Spring狀態機功能比較豐富,支持子狀態等,而我們業務需求比較復雜,同時有子狀態等業務場景,它可以滿足我們的業務需求。

3)Spring狀態機提供狀態機事件監聽器,允許開發者監聽狀態機的各種事件,如狀態變化、轉換觸發等,從而執行相應的業務邏輯。我們的關戶需求,也需要在關戶申請、申訴通過等節點觸發時進行一些通用業務邏輯。

4)我們在Spring狀態機基礎上擴展出事務消息,主要是基于Mysql數據庫表的事務實現。

5)擴展出失敗重試功能,主要基于XXL-JOB定時任務掃描失敗數據并進行重試。

其實架構選型,沒有銀彈,也難有通用解決方案,大家還是需要結合自己的業務做選型及改造。

參考部分網上資料,如有侵權,聯系我們。

關于作者黃培祖,采貨俠開發工程師

參考資料

[1] https://github.com/alibaba/COLA

[2] https://spring.io/projects/spring-statemachine

[3] https://blog.csdn.net/significantfrank/article/details/104996419

責任編輯:武曉燕 來源: 轉轉技術
相關推薦

2023-07-17 18:39:27

業務系統架構

2022-04-07 17:30:31

Flutter攜程火車票渲染

2021-01-12 11:02:56

云計算云存儲技術云開發

2021-01-12 11:13:11

云備份云原生云平臺

2019-02-12 05:34:25

2014-09-11 15:05:40

驅動設計驅動開發

2020-09-27 14:24:58

if-else cod業務

2013-08-15 09:00:49

云解決方案云服務

2023-06-28 06:33:37

2012-01-11 13:38:15

移動BI解決方案

2022-07-04 19:02:06

系統業務思考

2022-03-10 16:01:58

NTT思科網絡

2011-02-15 13:21:17

業務連續性安全威脅

2021-02-25 11:15:38

備份解決方案存儲

2011-01-21 09:53:12

2011-11-30 13:08:55

企業防毒防毒方案拯救三

2022-05-28 16:08:04

前端

2013-07-17 09:09:47

IAMEMCAveksa

2023-05-30 07:56:23

代碼軟件開發

2010-09-02 17:01:04

APC
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 免费视频成人国产精品网站 | 午夜在线视频一区二区三区 | 日韩精品一区二区三区久久 | 亚洲精品三级 | 7799精品视频天天看 | 亚洲一区二区三区在线播放 | 天堂久久久久久久 | 中文日韩在线 | 国产黄色一级电影 | 激情婷婷成人 | 91精品久久久久久久久久 | 中文字幕成人在线 | 狠狠爱免费视频 | 久久久久久久久久久久久9999 | 国产精品视频不卡 | 韩日一区二区 | 一二区成人影院电影网 | 在线免费av电影 | a免费视频| 另类在线 | 免费在线观看av网站 | 成人激情视频在线播放 | 国产精品区一区二 | 中文字幕一区二区三区四区五区 | 精品三级在线观看 | 日韩精品成人在线 | 亚洲视频在线看 | 中文字幕在线观看精品 | 久久久久久亚洲精品 | 国产日韩欧美一区二区 | 日本在线网址 | www.三级| 亚洲精品久久久一区二区三区 | 99精品国产一区二区三区 | 美国a级毛片免费视频 | 国产精品视频在线播放 | av黄色在线| 四虎在线播放 | 国产高清在线精品一区二区三区 | 免费毛片网站 | 久久精品国产亚洲 |