天天看點

Spring StateMachine學習(四) —— 持久化

在實際業務中,狀态機可能需要在某個環節停留,等待其他業務的觸發,然後再繼續下面的流程。比如訂單,可能在支付環節需要等待一個剁手的使用者隔天再下單,是以這裡面涉及到一個建立的狀态機該何去何從的問題。在spring statemachine中,給出來的辦法就是儲存起來,到需要的時候取出來用。

持久化到本地記憶體

首先要實作

StateMachinePersist

接口,這個接口非常簡單,就是write和read,用來讀寫

StateMachineContext

@Component
public class InMemoryStateMachinePersist implements StateMachinePersist<RefundReasonStatus, RefundReasonEvents, String> {
    private Map<String, StateMachineContext<RefundReasonStatus, RefundReasonEvents>> refundReasonMap =
            new HashMap<>();

    @Override
    public void write(StateMachineContext<RefundReasonStatus, RefundReasonEvents> stateMachineContext, String orderSn) throws Exception {
        refundReasonMap.put(orderSn, stateMachineContext);
    }

    @Override
    public StateMachineContext<RefundReasonStatus, RefundReasonEvents> read(String orderSn) throws Exception {
        return refundReasonMap.get(orderSn);
    }
}
           

InMemoryStateMachinePersist

儲存的對象是StateMachineContext,不是StateMachine,是以我們還不能直接用它,需要配置一下。

@Configuration
public class PersistConfig {


    @Autowired
    private InMemoryStateMachinePersist inMemoryStateMachinePersist;

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

}
           

然後我們就能在controller裡面使用了。

@Autowired
    @Qualifier("orderMemoryPersister")
    private StateMachinePersister<RefundReasonStatus, RefundReasonEvents, String> orderMemorypersister;
    
    //儲存狀态機
    @GetMapping("/testMemoryPersister")
    public void tesMemorytPersister(@RequestParam String orderSn) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        stateMachine.start();

        stateMachine.sendEvent(RefundReasonEvents.REJECT);
        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setMobile("13613650996");
        order.setSkuId(98765L);

        //持久化stateMachine
        orderMemorypersister.persist(stateMachine, orderSn);
    }

    //取出狀态機
    @GetMapping("/testMemoryPersisterRestore")
    public void testMemoryRestore(String id) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        orderMemorypersister.restore(stateMachine, id);
        log.info("恢複狀态機後的狀态為:{}", stateMachine.getState().getId());
    }
           

持久化到redis

真正的業務中,一般都是多台機分布式運作,是以如果狀态機隻能儲存在本地内容,就不能用在分布式應用上了。spring提供了一個友善的辦法,使用redis解決這個問題。讓我們看看怎麼弄。

pom檔案引入

spring-statemachine-redis

<dependency>
            <groupId>org.springframework.statemachine</groupId>
            <artifactId>spring-statemachine-redis</artifactId>
            <version>1.2.14.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

@Autowired
    private RedisConnectionFactory redisConnectionFactory;
    
    /**
     * 注入RedisStateMachinePersister對象
     *
     * @return
     */
    @Bean(name = "orderRedisPersister")
    public RedisStateMachinePersister<RefundReasonStatus, RefundReasonEvents> redisPersister() {
        return new RedisStateMachinePersister<>(redisPersist());
    }

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

這個套路和上面儲存到本地記憶體是一樣一樣的,先生成一個StateMachinePersist,這裡是通過RedisConnectionFactory生成RepositoryStateMachinePersist,然後再包裝輸出StateMachinePersister,這裡是RedisStateMachinePersister。

使用redis儲存和讀取狀态機

@Autowired
    @Qualifier("orderRedisPersister")
    private StateMachinePersister<RefundReasonStatus, RefundReasonEvents, String> orderRedisPersister;
    
    @GetMapping("/testRedisPersister")
    public void testRedisPersister(@RequestParam String orderSn) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        stateMachine.start();

        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setMobile("13613650996");
        order.setSkuId(98765L);
        Message<RefundReasonEvents> message = MessageBuilder.withPayload(RefundReasonEvents.APPROVE)
                .setHeader("order", order).build();
        stateMachine.sendEvent(message);
        //持久化stateMachine
        orderRedisPersister.persist(stateMachine, order.getOrderSn());
    }

    @GetMapping("/testRedisPersisterRestore")
    public void testRestore(String id) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        orderRedisPersister.restore(stateMachine, id);
        System.out.println("恢複狀态機後的狀态為:" + stateMachine.getState().getId());
    }
           

從redis中可以看到儲存的資訊。

Spring StateMachine學習(四) —— 持久化

僞持久化

我們設想一個業務場景,就比如訂單吧,我們一般的設計都會把訂單狀态存到訂單表裡面,其他的業務資訊也都有表儲存,而狀态機的主要作用其實是規範整個訂單業務流程的狀态和事件,是以狀态機要不要儲存真的不重要,我們隻需要從訂單表裡面把狀态取出來,知道目前是什麼狀态,然後伴随着業務繼續流浪到下一個狀态節點就好了。

我們先實作一個StateMachinePersist。這裡并不需要持久化。

@Component
public class OrderStateMachinePersist implements StateMachinePersist<RefundReasonStatus, RefundReasonEvents, Order> {

    @Override
    public void write(StateMachineContext<RefundReasonStatus, RefundReasonEvents> context, Order contextObj) throws Exception {
        //這裡不做任何持久化工作
    }

    @Override
    public StateMachineContext<RefundReasonStatus, RefundReasonEvents> read(Order order) throws Exception {
        StateMachineContext<RefundReasonStatus, RefundReasonEvents> result =
                new DefaultStateMachineContext<RefundReasonStatus, RefundReasonEvents>(RefundReasonStatus.valueOf(order.getStatus()),
                null, null, null, null, "refundReasonMachine");
        return result;
    }
}
           

然後在PersistConfig裡面轉換成StateMachinePersister。

@Autowired
    private OrderStateMachinePersist orderStateMachinePersist;

    @Bean(name="orderPersister")
    public StateMachinePersister<RefundReasonStatus, RefundReasonEvents, Order> orderPersister() {
        return new DefaultStateMachinePersister(orderStateMachinePersist);
    }
           

這樣主要是為了取一個任何狀态節點的狀态機。

@Autowired
    @Qualifier("orderPersister")
    private StateMachinePersister<RefundReasonStatus, RefundReasonEvents, Order> persister;
    
    @GetMapping("/testOrderRestore")
    public void testOrderRestore(@RequestParam String orderSn) throws Exception {
        StateMachine<RefundReasonStatus, RefundReasonEvents> stateMachine = refundReasonMachineBuilder.build(beanFactory);
        //訂單
        Order order = new Order();
        order.setOrderSn(orderSn);
        order.setStatus(4);
        //恢複
        persister.restore(stateMachine, order);
        //檢視恢複後狀态機的狀态
        System.out.println("恢複後的狀态:" + stateMachine.getState().getId());
    }
           

用builder建了一個新的狀态機,用restore過了一手,就已經是一個到達order指定狀态的老司機狀态機了,在這裡,持久化不是本意,讓狀态機能夠随時抓換到任意狀态節點才是目的。在實際的企業開發中,不可能所有情況都是從頭到尾的按狀态流程來,會有很多意外,比如曆史資料,故障重新開機後的遺留流程…,是以這種可以任意調節狀态的才是我們需要的狀态機。