28. Persisting State Machine

传统上,状态机的实例在正在运行的程序中使用。 更动态的行为可以通过允许状态机按需实例化的动态构建器和工厂来实现。 构建状态机的一个实例是相当繁重的操作,所以如果需要即使用状态机来处理数据库中的任意状态改变,我们也需要找到一种更好更快的方式来实现它。

持久性功能允许用户将状态机本身的状态保存到外部存储库中,然后根据序列化状态重置状态机。 例如,如果您有一个数据库表保持订单,那么如果需要为每个更改构建一个新实例,那么通过状态机更新订单状态将会非常昂贵。 持久性功能允许您在不实例化新状态机实例的情况下重置状态机状态。

有一个配方第35章,坚持和一个样本第42章,坚持它提供了更多关于持久状态的信息。

尽管可以使用StateMachineListener构建自定义持久性功能,但它有一个概念性问题。 当听者通知状态改变时,状态改变已经发生。 如果侦听器中的自定义持久方法无法更新外部存储库中的序列化状态,则状态机中的状态和外部存储库中的状态将处于不一致状态。

可以使用状态机拦截器来代替尝试将序列化状态保存到外部存储器的位置,这是在状态机内的状态更改期间完成的。 如果此拦截器回调失败,状态更改尝试将暂停,而不是结束为不一致的状态,则用户可以手动处理此错误。 使用拦截器在第24章“状态机拦截器”中讨论。

28.1 Using StateMachineContext

使用普通的Java序列化来持久化StateMachine是不可能的,因为对象图过于丰富并且对其他Spring上下文类有太多的依赖关系。 StateMachineContext是状态机的运行时表示,可用于将现有机器恢复到由特定StateMachineContext对象表示的状态。

28.2 Using StateMachinePersister

建立一个StateMachineContext,然后从中恢复一个状态机,如果手动完成,一直是一个黑魔法。 Interface StateMachinePersister旨在通过提供持久性和恢复方法来缓解这些操作。 这个接口的默认实现是DefaultStateMachinePersister

使用StateMachinePersister很容易通过测试中的代码片段来演示。 我们首先创建两个类似的状态机machine1和machine2的配置。 我们可以使用各种其他方式为演示构建不同的机器,但这是为这种情况服务的。

@Configuration
@EnableStateMachine(name = "machine1")
static class Config1 extends Config {
}

@Configuration
@EnableStateMachine(name = "machine2")
static class Config2 extends Config {
}

static class Config extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
        states
            .withStates()
                .initial("S1")
                .state("S1")
                .state("S2");
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
        transitions
            .withExternal()
                .source("S1")
                .target("S2")
                .event("E1");
    }
}

当我们使用StateMachinePersist时,我们只需创建一个内存中的实现。

内存中的示例仅用于演示目的,请使用真正的持久存储实现。

static class InMemoryStateMachinePersist implements StateMachinePersist<String, String, String> {

    private final HashMap<String, StateMachineContext<String, String>> contexts = new HashMap<>();

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

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

在我们实例化了两台不同的机器之后,我们可以通过事件E1将machine1转移到状态S2,然后持久化它并恢复machine2。

InMemoryStateMachinePersist stateMachinePersist = new InMemoryStateMachinePersist();
StateMachinePersister<String, String, String> persister = new DefaultStateMachinePersister<>(stateMachinePersist);

StateMachine<String, String> stateMachine1 = context.getBean("machine1", StateMachine.class);
StateMachine<String, String> stateMachine2 = context.getBean("machine2", StateMachine.class);
stateMachine1.start();

stateMachine1.sendEvent("E1");
assertThat(stateMachine1.getState().getIds(), contains("S2"));

persister.persist(stateMachine1, "myid");
persister.restore(stateMachine2, "myid");
assertThat(stateMachine2.getState().getIds(), contains("S2"));

28.3 Using Redis

通过实现StateMachinePersist的RepositoryStateMachinePersist完成对状态机持久化到Redis的支持。 具体实现是RedisStateMachineContextRepository,它使用kryo序列化将StateMachineContext保存到Redis中。

对于StateMachinePersister,我们有一个redis相关的RedisStateMachinePersister实现,它实现一个StateMachinePersist的实例并使用String作为其上下文对象。

请参阅第47章“事件服务”以了解详细用法。

RedisStateMachineContextRepository需要一个RedisConnectionFactory才能工作,我们推荐使用JedisConnectionFactory作为上面的示例。

28.4 Using StateMachineRuntimePersister

StateMachineRuntimePersister是StateMachinePersist的一个简单扩展,它添加接口级别方法来获取与之关联的StateMachineInterceptor。 这个拦截器需要在状态改变时保持机器而不需要停止和启动机器。

目前,这个接口的实现为即用型支持的Spring Data Repositories提供。 这些是JpaStateMachineRuntimePersister,RedisStateMachineRuntimePersister和MongoDbStateMachineRuntimePersister。

results matching ""

    No results matching ""