Spring Boot Integration

Spring Framework Logo 2018

MicroStream comes with a Spring Boot integration. It is available within this artifact:

pom.xml
<dependencies>
   <dependency>
      <groupId>one.microstream</groupId>
      <artifactId>microstream-integrations-spring-boot</artifactId>
      <version>08.01.02-MS-GA</version>
   </dependency>
</dependencies>

Official Spring Boot site: https://spring.io/projects/spring-boot

The integration requires Spring Boot 2.x (with version 2.1.0.RELEASE being the minimal supported one).

If you are running Spring Boot 3, use the following dependency in your project:

pom.xml
<dependencies>
   <dependency>
      <groupId>one.microstream</groupId>
      <artifactId>microstream-integrations-spring-boot3</artifactId>
      <version>08.01.02-MS-GA</version>
   </dependency>
</dependencies>

The migration from version 2 to 3 doesn’t require any code changes related to the MicroStream integration classes. Internally, the integration makes now use of the AutoConfiguration option (where the Spring Boot 2 integration code makes use of the Spring Factories config file)

Breaking changes

Since version 8.0.0, the StorageManager that is instantiated by the integration is also started where the previous version of the integration did not start the manager. You can have the old behaviour of a StorageManager that is not started by specifying the config value one.microstream.auto-start=false.

Configuration

The configuration of the StorageManager can be done using key/value pairs that are provided by Spring Boot external Configuration. The configuration keys must be prefixed by one.microstream

one.microstream.storage-directory=/opt/data/microstream
one.microstream.channel-count=2

The list of all MicroStream configuration properties and their meaning are listed on our documentation page.

The configuration values are handled using the typesafe configuration approach, and you can read these values by accessing the MicrostreamConfigurationProperties Spring bean.

You can either create a StorageManager yourself from an EmbeddedStorageFoundation Spring bean or access a fully configured one through the StorageManager Spring bean. Be aware that since both share the same configuration values, if you create and start a StorageManager from EmbeddedStorageFoundation, it will conflict with the one from the StorageManager Spring bean. When you use the EmbeddedStorageFoundation Spring bean, don’t access the StorageManager bean.

The EmbeddedStorageFoundation one is ideal if your Spring application is performing one-of tasks like for example Batch processing. The other one is suited in most cases and the StorageManager can be customized and initialized before it is actually used. Also, note that when using the feature of having the Root object as Spring Bean already creates and initializes the StorageManager.

The StorageManager configuration can be customized by Spring beans that implement the interface one.microstream.integrations.spring.boot.types.config.EmbeddedStorageFoundationCustomizer. The customize method is called with an EmbeddedStorageFoundation which allows you to fully customize the StorageManager that will be created. You can for example, add the specific Type Handlers for JDK 8 as described on the documentation.

After the StorageManager is created, the Spring beans that implement one.microstream.integrations.spring.boot.types.config.StorageManagerInitializer are called. You have the opportunity to perform actions on the StorageManager or root object. Following rules apply to the StorageManager that is passed to the initialize method of the interface.

  • The StorageManager is already started unless you specified the configuration value one.microstream.auto-start=false.

  • If you have used the @Storage annotation on a class, the StorageManager is already associated with an instance of that class as the Root object.

Root object

The root object can be indicated by using the @Storage annotation on the class. This annotation converts the POJO into a Spring bean (The annotation is a Spring Qualifier that makes the class also a Component).

Besides converting it into a Spring bean, any field or setter injection within this class is also resolved. Please note that constructor injection is not supported and will result in an error indicating that the class has not a no-argument constructor.

The integration also defines the instance of the class that is created as the root object (StorageManager.setRoot()) and stores the initial value (StorageManager.storeRoot()) when storageManager does not have a root object assigned yet (this happens only the very first time when you start up your application and the storage doesn’t contain any data yet)

You can only annotate 1 class with the @Storage annotation, if you have marked multiple, the creation of the Storage Spring bean will fail with a BeansException.

When using the @Storage functionality, you as a developer should not change the root object of the StorageManager yourself anymore as that will lead to conflicts and mismatches between the Spring bean created for the Root object and your newly set instance on the StorageManager.

When using @Storage, the Root initialization when there is no data in the data storage yet, happens asynchronously from the StorageManager initialization. This means that your code that uses the StorageManager bean can retrieve/operate on a null root when initialization is still in progress.

Multiple Managers

As of version 8.0.0, the integration support multiple StorageManagers. Within Spring (Boot) you can define multiple beans that implement the same interface by assigning it a certain label with the @Qualifier annotation. Using the same principle, you can define multiple StorageManagers. And in this case, also the Root object (through the @Storage annotation) and StorageManagerInitializer and EmbeddedStorageFoundationCustomizer concepts are supported.

Since we cannot know which qualifier (label) you want to use for your different _StorageManager_s, the Spring Boot integration cannot create the beans without a little help/configuration from the developer.

You can define the StorageManager you need and assign through the following Configuration Bean.

@Configuration
public class DefineStorageManagers {

    private final StorageManagerProvider provider;

    public DefineStorageManagers(StorageManagerProvider provider) {
        this.provider = provider;
    }

    @Bean
    @Qualifier("green")
    public EmbeddedStorageManager getGreenManager() {
        return provider.get(DatabaseColor.GREEN.getName());
    }
    @Bean
    @Qualifier("red")
    public EmbeddedStorageManager getRedManager() {
        return provider.get(DatabaseColor.RED.getName());
    }
}

The StorageManagerProvider is a helper bean from the Spring Boot integration that can fully initialise the StorageManager and the root by providing a qualifier label.

The qualifier label is used as prefix to look for the appropriate configuration values.

one.microstream.red.storage-directory=red-db
one.microstream.red.channel-count=2

one.microstream.green.storage-directory=green-db
one.microstream.green.channel-count=1

A StorageManagerInitializer`and `EmbeddedStorageFoundationCustomizer implementation can check which instance it received by looking at the database name property which reflects the Qualifier label that you used.

    @Override
    public void initialize(final StorageManager storageManager) {
        if (!"red".equals(storageManager.databaseName())) {
            // This customizer operates on the Red database only
            return;
        }
        /// Perform the required initialization.
    }

Another option is that you annotate the class with @Qualifier and the Initializer or Customizer is only called for items with matching qualifier label in that case.

Instead of 2 named StorageManager s through a Qualifier, you can also use one default (since we define a @Primary annotated StorageManager within the integration) and one that you define yourself as we have done above.

In that case, the configuration keys that you need to use are one.microstream. and one.microstream.<name> and the database name for the default one is Primary.

Late initialization

By default, Spring creates all singleton beans at the start of the application. The Spring beans defined by the MicroStream integration, like StorageManager and Storage root bean, are singletons. So they are created at startup which means that for example when you are using a database as a storage target, the database must be available and accessible when the application starts up.

When this is not desired, because the database might be only available when the user request arrives and not at application startup, you can use the Provider option.

Add the Jakarta Inject dependency to your project

    <dependency>
        <groupId>jakarta.inject</groupId>
        <artifactId>jakarta.inject-api</artifactId>
        <version>1.0</version>
    </dependency>

And use injection based on the Provider and not the actual class itself.

    private final Provider<StorageManager> storageManagerProvider;

    public UserRepository(Provider<StorageManager> storageManagerProvider) {

        this.storageManagerProvider = storageManagerProvider;
    }

When you need to access the StorageManager Spring bean, you perform storageManagerProvider.get() statement, and only at that point the StorageManager is created as a Spring bean. This allows you to delay the creation until the first user request.

Logging

MicroStream Spring module supports standard Spring logging, so you can add this into your config:<br> logging.level.one.microstream=debug to obtain all MicroStream configuration keys:

2021-08-23 15:16:02.979 DEBUG 18469 --- [           main] o.m.spring.MicrostreamConfiguration      : Microstream configuration items:
2021-08-23 15:16:02.979 DEBUG 18469 --- [           main] o.m.spring.MicrostreamConfiguration      : storage-filesystem.sql.postgres.password : xxxxx
2021-08-23 15:16:02.994 DEBUG 18469 --- [           main] o.m.spring.MicrostreamConfiguration      : storage-filesystem.sql.postgres.data-source-provider : one.microstream.test.spring.MyDataSourceProvider
2021-08-23 15:16:02.994 DEBUG 18469 --- [           main] o.m.spring.MicrostreamConfiguration      : storage-directory : microstream_storage
2021-08-23 15:16:02.994 DEBUG 18469 --- [           main] o.m.spring.MicrostreamConfiguration      : storage-filesystem.sql.postgres.user : postgres

Key values containing "password" are replaced by "xxxxx".