29. Using Distributed States

Distributed state is probably one of a most complicated concepts of a Spring State Machine. What exactly is a distributed state? A state within a single state machine is naturally really simple to understand but when there is a need to introduce a shared distributed state through a state machines, things will get a little complicated.

[Note]Note

Distributed state functionality is still a preview feature and is not yet considered to be stable in this particular release. We expect this feature to mature towards the first official release.

For generic configuration support see section Section 10.9, “Configuring Common Settings” and actual usage example see sample Chapter 41, Zookeeper.

Distributed State Machine is implemented via a DistributedStateMachine class which simply wraps an actual instance of a StateMachine. DistributedStateMachine intercepts communication with a StateMachine instance and works with distributed state abstractions handled via interface StateMachineEnsemble. Depending on an actual implementation StateMachinePersist interface may also be used to serialize a StateMachineContext which contains enough information to reset a StateMachine.

While Distributed State Machine is implemented via an abstraction, only one implementation currently exists based on Zookeeper.

Here is a generic example of how Zookeeper based Distributed State Machine would be configured.

@Configuration
@EnableStateMachine
public class Config
        extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<String, String> config)
            throws Exception {
        config
            .withDistributed()
                .ensemble(stateMachineEnsemble())
                .and()
            .withConfiguration()
                .autoStartup(true);
    }

    @Override
    public void configure(StateMachineStateConfigurer<String, String> states)
            throws Exception {
        // config states
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitions)
            throws Exception {
        // config transitions
    }

    @Bean
    public StateMachineEnsemble<String, String> stateMachineEnsemble()
            throws Exception {
        return new ZookeeperStateMachineEnsemble<String, String>(curatorClient(), "/zkpath");
    }

    @Bean
    public CuratorFramework curatorClient()
            throws Exception {
        CuratorFramework client = CuratorFrameworkFactory
                .builder()
                .defaultData(new byte[0])
                .connectString("localhost:2181").build();
        client.start();
        return client;
    }

}

Current technical documentation of a Zookeeker based distributed state machine can be found from an appendice Appendix C, Distributed State Machine Technical Paper.

29.1 ZookeeperStateMachineEnsemble

ZookeeperStateMachineEnsemble itself needs two mandatory settings, an instance of curatorClient and basePath. Client is a CuratorFramework and path is root of a tree in a Zookeeper.

Optionally it is possible to set cleanState which defaults to TRUE and will clear existing data if no members exists in an ensemble. Set this to FALSE if you want to preserve distributed state within application restarts.

Optionally it is possible to set a size of a logSize which defaults to 32 and is used to keep history of state changes. Value of this setting needs to be a power of two. 32 is generally good default value but if a particular state machine is left behind more than a size of a log it is put into error state and disconnected from an ensemble indicating it has lost its history to reconstruct fully synchronized status.