32. Repository Config Support

It is also possible to keep machine configuration in an external storage where it will be loaded on demand instead of creating a static configuration either using JavaConfig or UML based config. This integration works via Spring Data Repository abstraction.

We have created special StateMachineModelFactory implementation called RepositoryStateMachineModelFactory which is able to use base repository interfaces StateRepository, TransitionRepository, ActionRepository and GuardRepository accompanied with base entity interfaces RepositoryState, RepositoryTransition, RepositoryAction and RepositoryGuard respectively.

Due to way how Entities and Repositories work in a Spring Data, from a user perspective read access can be fully abstracted as it is done in RepositoryStateMachineModelFactory as there is no need to know what is a real mapped Entity class Repository is working with. Writing into a Repository is always dependant of using a real Repository specific Entity class. From machine configuration point of view we don’t need to know these, meaning we don’t need to know actual implementation whether that is JPA, Redis or anything else what Spring Data supports. Using a real Repository related Entity class comes into play when you manually try to write new states or transitions into a backed repository.

[Tip]Tip

Entity classes for RepositoryState and RepositoryTransition have machineId field which is in users disposal and can be used to differentiate between configurations for example if machines are built via StateMachineFactory.

Actual out of a box implementations are documented in below sections where images below are uml equivalent statecharts of a repository configs.

Figure 32.1. SimpleMachine

sm repository simplemachine

Figure 32.2. SimpleSubMachine

sm repository simplesubmachine

Figure 32.3. ShowcaseMachine

sm repository showcasemachine

32.1 JPA

Actual Repository implementations for a JPA are JpaStateRepository, JpaTransitionRepository, JpaActionRepository and JpaGuardRepository which are backed by Entity classes JpaRepositoryState, JpaRepositoryTransition, JpaRepositoryAction and JpaRepositoryGuard respectively.

Generic way to update states and transition manually for jpa is shown below. This is equivalent to machine shown in Figure 32.1, “SimpleMachine”.

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

void addConfig() {
    JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
    JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
    JpaRepositoryState stateS3 = new JpaRepositoryState("S3");

    stateRepository.save(stateS1);
    stateRepository.save(stateS2);
    stateRepository.save(stateS3);

    JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
    JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS2, stateS3, "E2");

    transitionRepository.save(transitionS1ToS2);
    transitionRepository.save(transitionS2ToS3);
}

This is equivalent to machine shown in Figure 32.2, “SimpleSubMachine”.

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

void addConfig() {
    JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
    JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
    JpaRepositoryState stateS3 = new JpaRepositoryState("S3");

    JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
    stateS21.setParentState(stateS2);
    JpaRepositoryState stateS22 = new JpaRepositoryState("S22");
    stateS22.setParentState(stateS2);

    stateRepository.save(stateS1);
    stateRepository.save(stateS2);
    stateRepository.save(stateS3);
    stateRepository.save(stateS21);
    stateRepository.save(stateS22);

    JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "E1");
    JpaRepositoryTransition transitionS2ToS3 = new JpaRepositoryTransition(stateS21, stateS22, "E2");
    JpaRepositoryTransition transitionS21ToS22 = new JpaRepositoryTransition(stateS2, stateS3, "E3");

    transitionRepository.save(transitionS1ToS2);
    transitionRepository.save(transitionS2ToS3);
    transitionRepository.save(transitionS21ToS22);
}

This is equivalent to machine shown in Figure 32.3, “ShowcaseMachine”.

First you access all repositories.

@Autowired
StateRepository<JpaRepositoryState> stateRepository;

@Autowired
TransitionRepository<JpaRepositoryTransition> transitionRepository;

@Autowired
ActionRepository<JpaRepositoryAction> actionRepository;

@Autowired
GuardRepository<JpaRepositoryGuard> guardRepository;

Create actions and guards.

JpaRepositoryGuard foo0Guard = new JpaRepositoryGuard();
foo0Guard.setName("foo0Guard");

JpaRepositoryGuard foo1Guard = new JpaRepositoryGuard();
foo1Guard.setName("foo1Guard");

JpaRepositoryAction fooAction = new JpaRepositoryAction();
fooAction.setName("fooAction");

guardRepository.save(foo0Guard);
guardRepository.save(foo1Guard);
actionRepository.save(fooAction);

Create states.

JpaRepositoryState stateS0 = new JpaRepositoryState("S0", true);
stateS0.setInitialAction(fooAction);
JpaRepositoryState stateS1 = new JpaRepositoryState("S1", true);
stateS1.setParentState(stateS0);
JpaRepositoryState stateS11 = new JpaRepositoryState("S11", true);
stateS11.setParentState(stateS1);
JpaRepositoryState stateS12 = new JpaRepositoryState("S12");
stateS12.setParentState(stateS1);
JpaRepositoryState stateS2 = new JpaRepositoryState("S2");
stateS2.setParentState(stateS0);
JpaRepositoryState stateS21 = new JpaRepositoryState("S21", true);
stateS21.setParentState(stateS2);
JpaRepositoryState stateS211 = new JpaRepositoryState("S211", true);
stateS211.setParentState(stateS21);
JpaRepositoryState stateS212 = new JpaRepositoryState("S212");
stateS212.setParentState(stateS21);

stateRepository.save(stateS0);
stateRepository.save(stateS1);
stateRepository.save(stateS11);
stateRepository.save(stateS12);
stateRepository.save(stateS2);
stateRepository.save(stateS21);
stateRepository.save(stateS211);
stateRepository.save(stateS212);

Finally create transitions.

JpaRepositoryTransition transitionS1ToS1 = new JpaRepositoryTransition(stateS1, stateS1, "A");
transitionS1ToS1.setGuard(foo1Guard);

JpaRepositoryTransition transitionS1ToS11 = new JpaRepositoryTransition(stateS1, stateS11, "B");
JpaRepositoryTransition transitionS21ToS211 = new JpaRepositoryTransition(stateS21, stateS211, "B");
JpaRepositoryTransition transitionS1ToS2 = new JpaRepositoryTransition(stateS1, stateS2, "C");
JpaRepositoryTransition transitionS1ToS0 = new JpaRepositoryTransition(stateS1, stateS0, "D");
JpaRepositoryTransition transitionS211ToS21 = new JpaRepositoryTransition(stateS211, stateS21, "D");
JpaRepositoryTransition transitionS0ToS211 = new JpaRepositoryTransition(stateS0, stateS211, "E");
JpaRepositoryTransition transitionS1ToS211 = new JpaRepositoryTransition(stateS1, stateS211, "F");
JpaRepositoryTransition transitionS2ToS21 = new JpaRepositoryTransition(stateS2, stateS21, "F");
JpaRepositoryTransition transitionS11ToS211 = new JpaRepositoryTransition(stateS11, stateS211, "G");

JpaRepositoryTransition transitionS0 = new JpaRepositoryTransition(stateS0, stateS0, "H");
transitionS0.setKind(TransitionKind.INTERNAL);
transitionS0.setGuard(foo0Guard);
transitionS0.setActions(new HashSet<>(Arrays.asList(fooAction)));

JpaRepositoryTransition transitionS1 = new JpaRepositoryTransition(stateS1, stateS1, "H");
transitionS1.setKind(TransitionKind.INTERNAL);

JpaRepositoryTransition transitionS2 = new JpaRepositoryTransition(stateS2, stateS2, "H");
transitionS2.setKind(TransitionKind.INTERNAL);
transitionS2.setGuard(foo1Guard);
transitionS2.setActions(new HashSet<>(Arrays.asList(fooAction)));

JpaRepositoryTransition transitionS11ToS12 = new JpaRepositoryTransition(stateS11, stateS12, "I");
JpaRepositoryTransition transitionS12ToS212 = new JpaRepositoryTransition(stateS12, stateS212, "I");
JpaRepositoryTransition transitionS211ToS12 = new JpaRepositoryTransition(stateS211, stateS12, "I");

JpaRepositoryTransition transitionS11 = new JpaRepositoryTransition(stateS11, stateS11, "J");
JpaRepositoryTransition transitionS2ToS1 = new JpaRepositoryTransition(stateS2, stateS1, "K");

transitionRepository.save(transitionS1ToS1);
transitionRepository.save(transitionS1ToS11);
transitionRepository.save(transitionS21ToS211);
transitionRepository.save(transitionS1ToS2);
transitionRepository.save(transitionS1ToS0);
transitionRepository.save(transitionS211ToS21);
transitionRepository.save(transitionS0ToS211);
transitionRepository.save(transitionS1ToS211);
transitionRepository.save(transitionS2ToS21);
transitionRepository.save(transitionS11ToS211);
transitionRepository.save(transitionS0);
transitionRepository.save(transitionS1);
transitionRepository.save(transitionS2);
transitionRepository.save(transitionS11ToS12);
transitionRepository.save(transitionS12ToS212);
transitionRepository.save(transitionS211ToS12);
transitionRepository.save(transitionS11);
transitionRepository.save(transitionS2ToS1);

Complete example can be found from sample Chapter 48, JPA Config. This example is also showing how repository can be pre-populated from existing json file having a definitions for entity classes.

32.2 Redis

Actual Repository implementations for a Redis are RedisStateRepository, RedisTransitionRepository, RedisActionRepository and RedisGuardRepository which are backed by Entity classes RedisRepositoryState, RedisRepositoryTransition, RedisRepositoryAction and RedisRepositoryGuard respectively.

Generic way to update states and transition manually for redis is shown below. This is equivalent to machine shown in Figure 32.1, “SimpleMachine”.

@Autowired
StateRepository<RedisRepositoryState> stateRepository;

@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;

void addConfig() {
    RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
    RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
    RedisRepositoryState stateS3 = new RedisRepositoryState("S3");

    stateRepository.save(stateS1);
    stateRepository.save(stateS2);
    stateRepository.save(stateS3);


    RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
    RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");

    transitionRepository.save(transitionS1ToS2);
    transitionRepository.save(transitionS2ToS3);
}

This is equivalent to machine shown in Figure 32.2, “SimpleSubMachine”.

@Autowired
StateRepository<RedisRepositoryState> stateRepository;

@Autowired
TransitionRepository<RedisRepositoryTransition> transitionRepository;

void addConfig() {
    RedisRepositoryState stateS1 = new RedisRepositoryState("S1", true);
    RedisRepositoryState stateS2 = new RedisRepositoryState("S2");
    RedisRepositoryState stateS3 = new RedisRepositoryState("S3");

    stateRepository.save(stateS1);
    stateRepository.save(stateS2);
    stateRepository.save(stateS3);


    RedisRepositoryTransition transitionS1ToS2 = new RedisRepositoryTransition(stateS1, stateS2, "E1");
    RedisRepositoryTransition transitionS2ToS3 = new RedisRepositoryTransition(stateS2, stateS3, "E2");

    transitionRepository.save(transitionS1ToS2);
    transitionRepository.save(transitionS2ToS3);
}

32.3 MongoDB

Actual Repository implementations for a MongoDB are MongoDbStateRepository, MongoDbTransitionRepository, MongoDbActionRepository and MongoDbGuardRepository which are backed by Entity classes MongoDbRepositoryState, MongoDbRepositoryTransition, MongoDbRepositoryAction and MongoDbRepositoryGuard respectively.

Generic way to update states and transition manually for redis is shown below. This is equivalent to machine shown in Figure 32.1, “SimpleMachine”.

@Autowired
StateRepository<MongoDbRepositoryState> stateRepository;

@Autowired
TransitionRepository<MongoDbRepositoryTransition> transitionRepository;

void addConfig() {
    MongoDbRepositoryState stateS1 = new MongoDbRepositoryState("S1", true);
    MongoDbRepositoryState stateS2 = new MongoDbRepositoryState("S2");
    MongoDbRepositoryState stateS3 = new MongoDbRepositoryState("S3");

    stateRepository.save(stateS1);
    stateRepository.save(stateS2);
    stateRepository.save(stateS3);

    MongoDbRepositoryTransition transitionS1ToS2 = new MongoDbRepositoryTransition(stateS1, stateS2, "E1");
    MongoDbRepositoryTransition transitionS2ToS3 = new MongoDbRepositoryTransition(stateS2, stateS3, "E2");

    transitionRepository.save(transitionS1ToS2);
    transitionRepository.save(transitionS2ToS3);
}

This is equivalent to machine shown in Figure 32.2, “SimpleSubMachine”.

@Autowired
StateRepository<MongoDbRepositoryState> stateRepository;

@Autowired
TransitionRepository<MongoDbRepositoryTransition> transitionRepository;

void addConfig() {
    MongoDbRepositoryState stateS1 = new MongoDbRepositoryState("S1", true);
    MongoDbRepositoryState stateS2 = new MongoDbRepositoryState("S2");
    MongoDbRepositoryState stateS3 = new MongoDbRepositoryState("S3");

    MongoDbRepositoryState stateS21 = new MongoDbRepositoryState("S21", true);
    stateS21.setParentState(stateS2);
    MongoDbRepositoryState stateS22 = new MongoDbRepositoryState("S22");
    stateS22.setParentState(stateS2);

    stateRepository.save(stateS1);
    stateRepository.save(stateS2);
    stateRepository.save(stateS3);
    stateRepository.save(stateS21);
    stateRepository.save(stateS22);

    MongoDbRepositoryTransition transitionS1ToS2 = new MongoDbRepositoryTransition(stateS1, stateS2, "E1");
    MongoDbRepositoryTransition transitionS2ToS3 = new MongoDbRepositoryTransition(stateS21, stateS22, "E2");
    MongoDbRepositoryTransition transitionS21ToS22 = new MongoDbRepositoryTransition(stateS2, stateS3, "E3");

    transitionRepository.save(transitionS1ToS2);
    transitionRepository.save(transitionS2ToS3);
    transitionRepository.save(transitionS21ToS22);
}