中文字幕精品亚洲无线码二区,国产黄a三级三级三级看三级,亚洲七七久久桃花影院,丰满少妇被猛烈进入,国产小视频在线观看网站

管(guan)理訂單(dan)狀態(tai),該上(shang)狀態(tai)機(ji)嗎?輕量級(ji)(ji)狀態(tai)機(ji)COLA StateMachine保姆(mu)級(ji)(ji)入門教程

前言

在平常(chang)的(de)后(hou)端項目開發(fa)中,狀(zhuang)(zhuang)態(tai)機(ji)模式(shi)的(de)使用其實沒有大家想象(xiang)中那么常(chang)見,筆者(zhe)之前由于(yu)不在電(dian)(dian)商領(ling)域工作,很少在業務(wu)代(dai)碼中用狀(zhuang)(zhuang)態(tai)機(ji)來管理各種狀(zhuang)(zhuang)態(tai),一(yi)般都是手動get/set狀(zhuang)(zhuang)態(tai)值(zhi)(zhi)。去年筆者(zhe)進入了電(dian)(dian)商領(ling)域從事后(hou)端開發(fa)。電(dian)(dian)商領(ling)域,狀(zhuang)(zhuang)態(tai)又多又復雜,如果仍(reng)然在業務(wu)代(dai)碼中東一(yi)塊西一(yi)塊維(wei)護狀(zhuang)(zhuang)態(tai)值(zhi)(zhi),很容易陷入出了問題難于(yu)Debug,難于(yu)追責的(de)窘境(jing)。

碰巧有個新(xin)啟動的(de)項目需(xu)(xu)要進行訂單(dan)狀(zhuang)(zhuang)態(tai)的(de)管(guan)理,我(wo)著手將Spring StateMachine接入了(le)進來(lai)(lai),管(guan)理購物(wu)訂單(dan)狀(zhuang)(zhuang)態(tai),不(bu)(bu)(bu)得不(bu)(bu)(bu)說(shuo),Spring StateMachine全(quan)家桶(tong)的(de)文檔寫的(de)是不(bu)(bu)(bu)錯,并且Spring StateMachine也是有官方背書的(de)。但是,它實在是太”重“了(le),想要簡單(dan)修(xiu)改一個訂單(dan)的(de)狀(zhuang)(zhuang)態(tai),需(xu)(xu)要十分復雜(za)的(de)代碼來(lai)(lai)實現。具體(ti)就(jiu)不(bu)(bu)(bu)在這里展開(kai)了(le),不(bu)(bu)(bu)然(ran)我(wo)感覺可以吐(tu)槽(cao)一整天(tian)。

說到(dao)底Spring StateMachine上手難度(du)非常(chang)大,如果沒(mei)有用來做重型(xing)狀態機的需求,十(shi)分(fen)不推薦普通的小(xiao)項(xiang)目進行接(jie)入。

最最重要的是,由于Spring StateMachine狀態機實例不是無狀態的,無法做到線程安全,所以代碼要么需要使用鎖同步,要么需要用Threadlocal,非常的痛苦和難用。 例如下面的Spring StateMachine代碼就(jiu)用了重量級(ji)鎖保證線(xian)程安全(quan),在(zai)高并發(fa)的互(hu)聯網應用中,這種代碼留的隱患非常大。

private synchronized boolean sendEvent(Message<PurchaseOrderEvent> message, OrderEntity orderEntity) {
        boolean result = false;
        try {
            stateMachine.start();
            // 嘗試恢復狀態機狀態
            persister.restore(stateMachine, orderEntity);
            // 執行事件
            result = stateMachine.sendEvent(message);
            // 持久化狀態機狀態
            persister.persist(stateMachine, (OrderEntity) message.getHeaders().get("purchaseOrder"));
        } catch (Exception e) {
            log.error("sendEvent error", e);
        } finally {
            stateMachine.stop();
        }
        return result;
    }

吃了一次虧后,我再一次在網上翻閱各種Java狀態機的實現,有大的開源項目,也有小而美的個人實現。結果在COLA架構中發現了COLA還寫了一套狀態機實現。COLA的作者給我們提供了一個無狀態的,輕量化的狀態機,接入十分簡單。并且由于無狀態的特點,可以做到線程安全,支持電商的高并發場景。

COLA是什么?如果(guo)你還沒聽說過(guo)COLA,不妨看(kan)(kan)一(yi)看(kan)(kan)我之前的文章,傳送門如下:

如果你需要在項目中引入狀態機,此時此刻,我會推薦使用COLA狀態機。

COLA狀態機介紹

COLA狀(zhuang)態機是(shi)在Github開源的,作者(zhe)也寫了介紹(shao)文章(zhang):

官方文章(zhang)的前半部分重點介紹了DSL(Domain Specific Languages),這一部分比較抽象(xiang)和概念化,大家感興趣,可以前往原文查看。我精(jing)簡一下DSL的主要含義:

什么是(shi)DSL? DSL是(shi)一種工具,它(ta)的核心價值(zhi)在(zai)于,它(ta)提供了(le)一種手段,可以(yi)更加清晰地就(jiu)系統某部分(fen)的意(yi)圖(tu)進行溝通。

比如正則表達式,/\d{3}-\d{3}-\d{4}/就(jiu)是(shi)(shi)一個(ge)典型的(de)(de)DSL,解(jie)決的(de)(de)是(shi)(shi)字符串(chuan)匹配這個(ge)特定(ding)領域的(de)(de)問題。

文章的后半部分重點闡述了作者為什么要做COLA狀態機?想(xiang)必(bi)這也是讀(du)者比較好奇的問題。我幫(bang)大家(jia)精(jing)簡一下原文的表述:

  • 首先,狀態機的實現應該可以非常的輕量,最簡單的狀態機用一個就能實現,基本是零成本。
  • 其次,使用狀態機的DSL來表達狀態的流轉,語義會更加清晰,會增強代碼的可讀性和可維護性
  • 開源狀態機太復雜: 就我們的項目而言(其實大部分項目都是如此)。我實在不需要那么多狀態機的高級玩法:比如狀態的嵌套(substate),狀態的并行(parallel,fork,join)、子狀態機等等
  • 開源狀態機性能差: 這些開源的狀態機都是有狀態的(Stateful)的,因為有狀態,狀態機的實例就不是線程安全的,而我們的應用服務器是分布式多線程的,所以在每一次狀態機在接受請求的時候,都不得不重新build一個新的狀態機實例。

所以COLA狀態機設計的目標很明確,有兩個核心理念:

  1. 簡潔的僅支持狀態流轉的狀態機,不需要支持嵌套、并行等高級玩法。
  2. 狀態機本身需要是Stateless(無狀態)的,這樣一個Singleton Instance就能服務所有的狀態流轉請求了。

COLA狀態機的核心概念如下圖所示,主要包括:

State:狀態
Event:事件,狀態由事件觸發,引起變化
Transition:流轉,表示從一個狀態到另一個狀態
External Transition:外部流轉,兩個不同狀態之間的流轉
Internal Transition:內部流轉,同一個狀態之間的流轉
Condition:條件,表示是否允許到達某個狀態
Action:動作,到達某個狀態之后,可以做什么
StateMachine:狀態機

COLA狀態機原理

這一小節,我們先講幾個COLA狀態機最重要兩個部分,一個是它使用的連貫接口,一個是狀態機的注冊和使用原理。如果你暫時對它的實現原理不感興趣,可以直接跳過本小節,直接看后面的實戰代碼部分。

PS:講解(jie)的代碼版本為(wei)cola-component-statemachine 4.2.0-SNAPSHOT

下圖(tu)展示了COLA狀(zhuang)態機的源代(dai)碼目(mu)錄,可以看到非(fei)常的簡潔。

1. 連貫接口 Fluent Interfaces

COLA狀態機的定義使用了連貫接口Fluent Interfaces,連貫接口的一個重要作用是,限定方法調用的順序。比如,在構建狀態機的時候,我們只有在調用了from方法后,才能調用to方法,Builder模式沒有這個功能。

下圖(tu)中(zhong)可以(yi)看到,我們在使用的(de)(de)時候(hou)是被(bei)嚴(yan)格限制的(de)(de):

StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
        builder.externalTransition()
                .from(States.STATE1)
                .to(States.STATE2)
                .on(Events.EVENT1)
                .when(checkCondition())
                .perform(doAction());

這(zhe)是(shi)如何實(shi)現的?其實(shi)是(shi)使用(yong)了(le)Java接口來實(shi)現。

2. 狀態機注冊和觸發原理

這里簡單梳理一下狀態機的(de)注冊和觸發原(yuan)理。

用戶執(zhi)行如下(xia)代碼(ma)來創建(jian)一(yi)個狀態機,指定一(yi)個MACHINE_ID:

StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);

COLA會將該狀(zhuang)態機在StateMachineFactory類中,放入一個ConcurrentHashMap,以狀(zhuang)態機名(ming)為key注冊。

static Map<String /* machineId */, StateMachine> stateMachineMap = new ConcurrentHashMap<>();

注冊好后,用戶便可以使用狀態(tai)機(ji),通過類似下方的代碼觸發狀態(tai)機(ji)的狀態(tai)流轉:

stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("1"));

內部實現如下:

  1. 首先判斷COLA狀態機整個組件是否初始化完成。
  2. 通過routeTransition尋找是否有符合條件的狀態流轉。
  3. transition.transit執行狀態流轉。

transition.transit方法中(zhong):

檢查本次流轉是否符合condition,符合,則執行對應的action。

COLA狀態機實戰

**PS:以下實戰(zhan)代(dai)碼取(qu)自COLA官(guan)方(fang)倉庫測試(shi)類

一、狀態流轉使用示例

  1. 從單一狀態流轉到另一個狀態
@Test
public void testExternalNormal(){
    StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
    builder.externalTransition()
            .from(States.STATE1)
            .to(States.STATE2)
            .on(Events.EVENT1)
            .when(checkCondition())
            .perform(doAction());

    StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID);
    States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());
    Assert.assertEquals(States.STATE2, target);
}

private Condition<Context> checkCondition() {
		return (ctx) -> {return true;};
}

private Action<States, Events, Context> doAction() {
    return (from, to, event, ctx)->{
        System.out.println(ctx.operator+" is operating "+ctx.entityId+" from:"+from+" to:"+to+" on:"+event);
        };
}

可以看到,每次進行(xing)狀態流轉時(shi),檢查(cha)checkCondition(),當返回true,執(zhi)行(xing)狀態流轉的操作doAction()。

后面所有的checkCondition()和doAction()方法在下方就不再重復貼出了。

  1. 從多個狀態流傳到新的狀態
@Test
public void testExternalTransitionsNormal(){
    StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
    builder.externalTransitions()
            .fromAmong(States.STATE1, States.STATE2, States.STATE3)
            .to(States.STATE4)
            .on(Events.EVENT1)
            .when(checkCondition())
            .perform(doAction());

    StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID+"1");
    States target = stateMachine.fireEvent(States.STATE2, Events.EVENT1, new Context());
    Assert.assertEquals(States.STATE4, target);
}
  1. 狀態內部觸發流轉
@Test
public void testInternalNormal(){
    StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
    builder.internalTransition()
            .within(States.STATE1)
            .on(Events.INTERNAL_EVENT)
            .when(checkCondition())
            .perform(doAction());
    StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID+"2");

    stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());
    States target = stateMachine.fireEvent(States.STATE1, Events.INTERNAL_EVENT, new Context());
    Assert.assertEquals(States.STATE1, target);
}
  1. 多線程測試并發測試
@Test
public void testMultiThread(){
	buildStateMachine("testMultiThread");

  for(int i=0 ; i<10 ; i++){
  	Thread thread = new Thread(()->{
      StateMachine<States, Events, Context> stateMachine = StateMachineFactory.get("testMultiThread");
      States target = stateMachine.fireEvent(States.STATE1, Events.EVENT1, new Context());
      Assert.assertEquals(States.STATE2, target);
      });
      thread.start();
    }


    for(int i=0 ; i<10 ; i++) {
      Thread thread = new Thread(() -> {
      StateMachine<States, Events, Context> stateMachine = StateMachineFactory.get("testMultiThread");
      States target = stateMachine.fireEvent(States.STATE1, Events.EVENT4, new Context());
      Assert.assertEquals(States.STATE4, target);
      });
      thread.start();
    }

    for(int i=0 ; i<10 ; i++) {
      Thread thread = new Thread(() -> {
      StateMachine<States, Events, Context> stateMachine = StateMachineFactory.get("testMultiThread");
      States target = stateMachine.fireEvent(States.STATE1, Events.EVENT3, new Context());
      Assert.assertEquals(States.STATE3, target);
      });
      thread.start();
  }

}

由于COLA狀(zhuang)態機時無狀(zhuang)態的狀(zhuang)態機,所以性(xing)能(neng)是(shi)很(hen)高的。相比起來,SpringStateMachine由于是(shi)有狀(zhuang)態的,就需(xu)要使(shi)用者自(zi)行保證線程安全(quan)了(le)。

二、多分支狀態流轉示例

/**
* 測試選擇分支,針對同一個事件:EVENT1
* if condition == "1", STATE1 --> STATE1
* if condition == "2" , STATE1 --> STATE2
* if condition == "3" , STATE1 --> STATE3
*/
@Test
public void testChoice(){
  StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, Context> builder = StateMachineBuilderFactory.create();
  builder.internalTransition()
  .within(StateMachineTest.States.STATE1)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition1())
  .perform(doAction());
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition2())
  .perform(doAction());
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE3)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition3())
  .perform(doAction());

  StateMachine<StateMachineTest.States, StateMachineTest.Events, Context> stateMachine = builder.build("ChoiceConditionMachine");
  StateMachineTest.States target1 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("1"));
  Assert.assertEquals(StateMachineTest.States.STATE1,target1);
  StateMachineTest.States target2 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("2"));
  Assert.assertEquals(StateMachineTest.States.STATE2,target2);
  StateMachineTest.States target3 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("3"));
  Assert.assertEquals(StateMachineTest.States.STATE3,target3);
  }

可以看到,編(bian)寫一個多分支的(de)(de)狀態(tai)機也是(shi)非常(chang)簡單明(ming)了的(de)(de)。

三、通過狀態機反向生成PlantUml圖

沒想到(dao)吧(ba),還能通過代碼定義好的狀態(tai)機反向生成plantUML圖(tu)(tu),實現狀態(tai)機的可視化。(可以用圖(tu)(tu)說話(hua),和產品對(dui)比下狀態(tai)實現的是否正確了。)

四、特殊使用示例

  1. 不滿足狀態流轉條件時的處理
@Test
public void testConditionNotMeet(){
  StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, StateMachineTest.Context> builder = StateMachineBuilderFactory.create();
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkConditionFalse())
  .perform(doAction());

  StateMachine<StateMachineTest.States, StateMachineTest.Events, StateMachineTest.Context> stateMachine = builder.build("NotMeetConditionMachine");
  StateMachineTest.States target = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new StateMachineTest.Context());
  Assert.assertEquals(StateMachineTest.States.STATE1,target);
}

可以(yi)看到,當(dang)checkConditionFalse()執行時(shi),永遠(yuan)不(bu)會(hui)滿足狀態流轉的條件,則(ze)狀態不(bu)會(hui)變化,會(hui)直接返回原來的STATE1。相(xiang)關源(yuan)碼(ma)在這里:

  1. 重復定義相同的狀態流轉
@Test(expected = StateMachineException.class)
public void testDuplicatedTransition(){
  StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, StateMachineTest.Context> builder = StateMachineBuilderFactory.create();
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition())
  .perform(doAction());

  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition())
  .perform(doAction());
}

會在(zai)第二次builder執行到on(StateMachineTest.Events.EVENT1)函數時,拋出(chu)StateMachineException異常(chang)。拋出(chu)異常(chang)在(zai)on()的verify檢(jian)查這里,如下:

  1. 重復定義狀態機
@Test(expected = StateMachineException.class)
public void testDuplicateMachine(){
  StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, StateMachineTest.Context> builder = StateMachineBuilderFactory.create();
  builder.externalTransition()
  .from(StateMachineTest.States.STATE1)
  .to(StateMachineTest.States.STATE2)
  .on(StateMachineTest.Events.EVENT1)
  .when(checkCondition())
  .perform(doAction());

  builder.build("DuplicatedMachine");
  builder.build("DuplicatedMachine");
}

會在第二次build同(tong)名狀態機(ji)時拋出(chu)StateMachineException異常。拋出(chu)異常的源碼在狀態機(ji)的注冊(ce)函數中,如下:

結語

為了不把篇幅拉得過(guo)(guo)長(chang),在這里無法詳細地橫向對比幾大(da)主(zhu)流狀態機(Spring Statemachine,Squirrel statemachine等(deng)(deng))和COLA的區(qu)別,不過(guo)(guo)基于筆者在Spring Statemachine踩過(guo)(guo)的深坑,目(mu)(mu)前來看,COLA狀態機的簡潔設計(ji)適合用(yong)在訂單管理等(deng)(deng)小型狀態機的維(wei)護(hu),如果(guo)你想(xiang)要在你的項目(mu)(mu)中接入狀態機,又不需要嵌套、并(bing)行等(deng)(deng)高級玩法,那么COLA是個十(shi)分(fen)合適的選擇。

我是后端工程師,蠻三刀醬。

持續的更新原創優質文章,離不開你的點贊,轉發和分享!

我的唯一技術(shu)公眾(zhong)號:后端技術(shu)漫談

posted @ 2022-06-02 22:18  蠻三刀醬  閱讀(6804)  評論(1)    收藏  舉報