SpringBot Seeding Data

While data can be created and edited from within the administration section. It can sometimes be annoying to have to re-create the data for every deployment and development setup. This is the same for a large amount of data. There is often a certain amount that we want to exist on a fresh installation of an application.


Core Data

To seed this initial data, we first want to create a CoreData class. This class will contain everything we need to seed all of the data we need in our application.

For this example we will be seeding a workflow from the Workflow behaviour.

ApplicationRunner Interface

Using the @Component annotation allows this bean to be registered with SpringBoot.

Now to ensure this class runs on the application startup, we implement the ApplicationRunner interface. This interface does one thing, ensures the run method is executed at application start.

@Component
@Slf4j
public class CoreData implements ApplicationRunner {
   ...

Dependency Injection

Given we are working with data, we will need to inject all of the required repositories into our new class. This is allowed due to us declaring class as a SpringBoot component.

private final WorkflowRepository workflowRepository;
private final WorkflowVersionRepository workflowVersionRepository;
private final WorkflowStateRepository workflowStateRepository;
private final WorkflowTransitionRepository workflowTransitionRepository;

@Autowired
public CoreData(
        WorkflowRepository workflowRepository,
        WorkflowVersionRepository workflowVersionRepository,
        WorkflowStateRepository workflowStateRepository,
        WorkflowTransitionRepository workflowTransitionRepository) {
    this.workflowRepository = workflowRepository;
    this.workflowVersionRepository = workflowVersionRepository;
    this.workflowStateRepository = workflowStateRepository;
    this.workflowTransitionRepository = workflowTransitionRepository;
}

...

AnonymousHelper

Now, given everything is controlled by our security model and in our startup data class we are not registered within our authentication context, we in theory cannot access anything.

To bypass this, we can make use of the AnonymousHelper.runAnonymously helper method that bypasses our security.

WARNING As this completely bypasses security, be very careful where you use this.

@Override
public void run(ApplicationArguments args) throws Exception {
    AnonymousHelper.runAnonymously(() -> {
        this.injectRegistrationWorkflow();
    });
}

Injection

Given our class now runs on startup, we have the appropriate permissions, and we have our repositories injected and ready to use, we can begin to inject our data.

This example sets up the registration workflow, additional methods like this can be created and added to the run method to insert more data.

You will notice before we do anything in this method, we check to ensure it has not already been run with the this.workflowVersionRepository.count() == 0 conditional.

This workflow will operate as follows.
Image

/**
* Inject the workflow states and transitions for Registration entity
*/
private void injectRegistrationWorkflow() {

    // Check to see if any already exist, only create if it is a fresh install
    if (this.workflowVersionRepository.count() == 0) {

        /* Setup Workflow */
        var workflowName = "Registration Workflow";
        var registrationWorkFlow = new WorkflowEntity();
        registrationWorkFlow.setName(workflowName);

        registrationWorkFlow = this.workflowRepository.save(registrationWorkFlow);

        var registrationWorkflowVersion = new WorkflowVersionEntity();
        registrationWorkflowVersion.setRegistrationAssociation(true);
        registrationWorkflowVersion.setWorkflowDescription("Workflow for  a patients registration.");
        registrationWorkflowVersion.setWorkflowName(workflowName);
        registrationWorkflowVersion.setWorkflow(registrationWorkFlow);

        registrationWorkflowVersion = workflowVersionRepository.save(registrationWorkflowVersion);

        /* Create states */
        var pendingState = new WorkflowStateEntity();
        pendingState.setStepName("Pending");
        pendingState.setIsStartState(true);
        pendingState.setWorkflowVersion(registrationWorkflowVersion);
        this.workflowStateRepository.save(pendingState);

        var bookedState = new WorkflowStateEntity();
        bookedState.setStepName("Booked");
        bookedState.setWorkflowVersion(registrationWorkflowVersion);
        this.workflowStateRepository.save(bookedState);

        var cancelledState = new WorkflowStateEntity();
        cancelledState.setStepName("Cancelled");
        cancelledState.setWorkflowVersion(registrationWorkflowVersion);
        this.workflowStateRepository.save(cancelledState);

        var completedState = new WorkflowStateEntity();
        completedState.setStepName("Completed");
        completedState.setWorkflowVersion(registrationWorkflowVersion);
        this.workflowStateRepository.save(completedState);

        /** Create transitions **/
        var pendingToBookedTransition = new WorkflowTransitionEntity();
        pendingToBookedTransition.setTransitionName("Booked");
        pendingToBookedTransition.setSourceState(pendingState);
        pendingToBookedTransition.setTargetState(bookedState);
        this.workflowTransitionRepository.save(pendingToBookedTransition);

        var pendingToCancelledTransition = new WorkflowTransitionEntity();
        pendingToCancelledTransition.setTransitionName("Cancelled");
        pendingToCancelledTransition.setTargetState(cancelledState);
        pendingToCancelledTransition.setSourceState(pendingState);
        this.workflowTransitionRepository.save(pendingToCancelledTransition);

        var bookedToCancelledTransition = new WorkflowTransitionEntity();
        bookedToCancelledTransition.setTransitionName("Cancelled");
        bookedToCancelledTransition.setSourceState(bookedState);
        bookedToCancelledTransition.setTargetState(cancelledState);
        this.workflowTransitionRepository.save(bookedToCancelledTransition);

        var bookedToCompletedTransition = new WorkflowTransitionEntity();
        bookedToCompletedTransition.setTransitionName("Completed");
        bookedToCompletedTransition.setSourceState(bookedState);
        bookedToCompletedTransition.setTargetState(completedState);
        this.workflowTransitionRepository.save(bookedToCompletedTransition);
    }
}

Test Data

Often, you will have data you want to run when developing or testing that do not want in production. This can be achieved by creating another class like the CoreData one above called TestData.

To ensure this new class only runs when in test mode, add the annotation @Profile({"test"}) to the class signature.

@Component
@Profile({"test"})
public class TestData implements ApplicationRunner {
    ...
}

This annotation can used for the other run profile that SpringBot supports, dev.

Last updated: 03 June 2020


Start modelling your app today.