spring statemachine的企業可用級開發指南6-持久化

目前爲止,咱們都是從狀態流程的開始階段建立一個狀態機,而後一路走下去。但在實際業務中,狀態機可能須要在某個環節停留,等待其餘業務的觸發,而後再繼續下面的流程。好比訂單,可能在支付環節須要等待一個剁手的用戶隔天再下單,因此這裏面涉及到一個建立的狀態機該何去何從的問題。在spring statemachine中,給出來的辦法就是保存起來,到須要的時候取出來用。java

一、持久化到本地內存
嚴格來講,你徹底能夠本身保存狀態機,好比我就本身用map保存下來了。git

public class MachineMap {redis

public static Map<String,StateMachine<OrderStates, OrderEvents>> orderMap = new HashMap<String,StateMachine<OrderStates, OrderEvents>>();
public static Map<String,StateMachine<FormStates, FormEvents>> formMap = new HashMap<String,StateMachine<FormStates, FormEvents>>();

}
這個代碼一看就明白,我用惟一id做爲key(order就是orderId,form就是formId),把狀態機保存到map表裏面,在實際的業務中,自由存取spring

@RequestMapping("/sendEvent")數據庫

void sendEvent(String machineId,String events,String id) throws Exception{
    if(machineId.equals("form")) {
        StateMachine sm = MachineMap.formMap.get(id);
        Form form = new Form();
        form.setId(id);
        if(sm == null) {
            if(events.equals("WRITE")) {
                sm = formStateMachineBuilder.build(beanFactory);
                sm.start();
                MachineMap.formMap.put(id, sm);
            }else {
                System.out.println("該表單流程還沒有開始,不能作"+events+"轉換");
                return;
            }
        }
        
        Message message = MessageBuilder.withPayload(FormEvents.valueOf(events)).setHeader("form", form).build();
        sm.sendEvent(message);
    }
    if(machineId.equals("order")) {
        StateMachine sm = MachineMap.orderMap.get(id);
        Order order = new Order();
        order.setId(id);
        if(sm == null) {
            if(events.equals("PAY")) {
                sm = orderStateMachineBuilder.build(beanFactory);
                sm.start();
                MachineMap.orderMap.put(id, sm);
            }else {
                System.out.println("該訂單流程還沒有開始,不能作"+events+"轉換");
                return;
            }
            
        }
        Message message = MessageBuilder.withPayload(OrderEvents.valueOf(events)).setHeader("order", order).setHeader("test","test1").build();
        sm.sendEvent(message);
    }
}

在這段代碼裏面,我根據不一樣種類的狀態機去map取和id對應的狀態機,若是狀態機沒在map裏面,就建立一個,而後塞進map表。其實寫這個代碼,並無其餘意思,只是爲了告訴你,spring打算管理你的map表,因此出了一個接口來規範這件事:緩存

import java.util.HashMap;
import java.util.Map;springboot

import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.stereotype.Component;服務器

/**app

  • 在內存中持久化狀態機

*/
@Component
public class InMemoryStateMachinePersist implements StateMachinePersist<OrderStates, OrderEvents, String> {分佈式

private Map<String, StateMachineContext<OrderStates, OrderEvents>> map = new HashMap<String, StateMachineContext<OrderStates,OrderEvents>>();

@Override
public void write(StateMachineContext<OrderStates, OrderEvents> context, String contextObj) throws Exception {
    map.put(contextObj, context);
}

@Override
public StateMachineContext<OrderStates, OrderEvents> read(String contextObj) throws Exception {
    return map.get(contextObj);
}

}
這個接口很是簡單,就是write和read,但他保存的對象是StateMachineContext,不是StateMachine,因此咱們還不能直接用它,須要配置一下。

import org.springframework.statemachine.StateMachinePersist;
......
import org.springframework.statemachine.persist.StateMachinePersister;
@Configuration
public class PersistConfig {

@Autowired
private InMemoryStateMachinePersist inMemoryStateMachinePersist;

/**
 * 注入StateMachinePersister對象
 * 
 * @return
 */
@Bean(name="orderMemoryPersister")
public StateMachinePersister<OrderStates, OrderEvents, String> getPersister() {
    return new DefaultStateMachinePersister<>(inMemoryStateMachinePersist);
}

}
這裏有個坑,InMemoryStateMachinePersist 實現的接口是

org.springframework.statemachine.StateMachinePersist
,但在PersistConfig 裏面,getPersister()方法返回的值類型是StateMachinePersister類型,看着很像,但並非上面的這個接口,而是org.springframework.statemachine.persist.StateMachinePersister接口,爲了表示這個坑對個人傷害,我要強調一下兩個接口:

package org.springframework.statemachine;
public interface StateMachinePersist<S, E, T> {

void write(StateMachineContext<S, E> context, T contextObj) throws Exception;
StateMachineContext<S, E> read(T contextObj) throws Exception;

}
package org.springframework.statemachine.persist;
import org.springframework.statemachine.StateMachine;
public interface StateMachinePersister<S, E, T> {

void persist(StateMachine<S, E> stateMachine, T contextObj) throws Exception;
StateMachine<S, E> restore(StateMachine<S, E> stateMachine, T contextObj) throws Exception;

}
這兩個接口名字很相似,很容易搞混,但下面的是有er的,包名也不一樣的。StateMachinePersister是能夠直接保存StateMachine對象的,因此咱們須要先實現上面的StateMachinePersist,而後再一個Config類裏面轉換成下面的StateMachinePersister,轉換的代碼就在上面的PersistConfig類裏。

而後咱們就能在controller裏面使用了

@Resource(name="orderMemoryPersister")
private StateMachinePersister<OrderStates, OrderEvents, String> orderMemorypersister;

......

//保存狀態機
@RequestMapping("/testMemoryPersister")

public void tesMemorytPersister(String id) throws Exception {
    StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
    stateMachine.start();
    
    //發送PAY事件
    stateMachine.sendEvent(OrderEvents.PAY);
    Order order = new Order();
    order.setId(id);
    
    //持久化stateMachine
    orderMemorypersister.persist(stateMachine, order.getId());

}

//取出狀態機
@RequestMapping("/testMemoryPersisterRestore")

public void testMemoryRestore(String id) throws Exception {
    StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
    orderMemorypersister.restore(stateMachine, id);
    System.out.println("恢復狀態機後的狀態爲:" + stateMachine.getState().getId());
}

二、持久化到redis
真正的業務中,通常都是多臺機分佈式運行,因此若是狀態機只能保存在本地內容,就不能用在分佈式應用上了。spring提供了一個方便的辦法,使用redis解決這個問題。讓咱們看看怎麼弄。

pom文件引入spring-statemachine-redis

<!-- redis持久化狀態機 -->

<dependency>
        <groupId>org.springframework.statemachine</groupId>
        <artifactId>spring-statemachine-redis</artifactId>
        <version>1.2.9.RELEASE</version>
    </dependency>

在springboot配置文件裏面加上redis參數,我這是application.properties

REDIS (RedisProperties)

Redis數據庫索引(默認爲0)

spring.redis.database=0

Redis服務器地址

spring.redis.host=localhost

Redis服務器鏈接端口

spring.redis.port=6379

Redis服務器鏈接密碼(默認爲空)

spring.redis.password=

鏈接池最大鏈接數(使用負值表示沒有限制)

spring.redis.pool.max-active=8

鏈接池最大阻塞等待時間(使用負值表示沒有限制)

spring.redis.pool.max-wait=-1

鏈接池中的最大空閒鏈接

spring.redis.pool.max-idle=8

鏈接池中的最小空閒鏈接

spring.redis.pool.min-idle=0

鏈接超時時間(毫秒)

spring.redis.timeout=0
保證配置的redis開啓並能用,咱們繼續。回到咱們熟悉的PersistConfig

@Configuration
public class PersistConfig {

@Autowired
private RedisConnectionFactory redisConnectionFactory;


/**
 * 注入RedisStateMachinePersister對象
 * 
 * @return
 */
@Bean(name = "orderRedisPersister")
public RedisStateMachinePersister<OrderStates, OrderEvents> redisPersister() {
    return new RedisStateMachinePersister<>(redisPersist());
}

/**
 * 經過redisConnectionFactory建立StateMachinePersist
 * 
 * @return
 */
public StateMachinePersist<OrderStates, OrderEvents,String> redisPersist() {
    RedisStateMachineContextRepository<OrderStates, OrderEvents> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
    return new RepositoryStateMachinePersist<>(repository);
}

}
這個套路和上面保存到本地內存是同樣同樣的,先生成一個StateMachinePersist,這裏是經過RedisConnectionFactory生成RepositoryStateMachinePersist,而後再包裝輸出StateMachinePersister,這裏是RedisStateMachinePersister。而後就能夠愉快的在controller裏面看怎麼用了

@Resource(name="orderRedisPersister")
private StateMachinePersister<OrderStates, OrderEvents, String> orderRedisPersister;

......

@RequestMapping("/testRedisPersister")

public void testRedisPersister(String id) throws Exception {
    StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
    stateMachine.start();
    Order order = new Order();
    order.setId(id);
    //發送PAY事件
    Message<OrderEvents> message = MessageBuilder.withPayload(OrderEvents.PAY).setHeader("order", order).build();
    stateMachine.sendEvent(message);
    //持久化stateMachine
    orderRedisPersister.persist(stateMachine, order.getId());
}

@RequestMapping("/testRedisPersisterRestore")
public void testRestore(String id) throws Exception {
    StateMachine<OrderStates, OrderEvents> stateMachine = orderStateMachineBuilder.build(beanFactory);
    orderRedisPersister.restore(stateMachine, id);
    System.out.println("恢復狀態機後的狀態爲:" + stateMachine.getState().getId());
}

執行完redis保存statemachine後,你們能夠本身在redis客戶端查看如下,是否是有內容保存進去了。

三、下期預告
不管是本地內存仍是分佈式緩存,彷佛都不是很可靠的持久化,但下一章會告訴你們,保不保存也就那麼點大的事,沒保存咱們就再建立一個就行了。但另一個問題就浮出水面了,狀態機執行到中間的時候建立狀態機,怎麼在中間繼續執行,以前的可都是從頭開始執行的,這個問題,下一章解決。

碼雲配套代碼地址

相關文章
相關標籤/搜索