{"id":20644128,"url":"https://github.com/rdebusscher/microstream-spring-boot-patterns","last_synced_at":"2025-04-16T02:07:10.728Z","repository":{"id":46595543,"uuid":"515136505","full_name":"rdebusscher/microstream-spring-boot-patterns","owner":"rdebusscher","description":"Patterns in using MicroStream's Spring Boot integration","archived":false,"fork":false,"pushed_at":"2023-02-06T13:46:32.000Z","size":106,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-16T02:06:40.826Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rdebusscher.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-07-18T10:26:29.000Z","updated_at":"2023-12-06T09:48:47.000Z","dependencies_parsed_at":"2023-02-09T12:01:43.097Z","dependency_job_id":null,"html_url":"https://github.com/rdebusscher/microstream-spring-boot-patterns","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rdebusscher%2Fmicrostream-spring-boot-patterns","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rdebusscher%2Fmicrostream-spring-boot-patterns/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rdebusscher%2Fmicrostream-spring-boot-patterns/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rdebusscher%2Fmicrostream-spring-boot-patterns/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rdebusscher","download_url":"https://codeload.github.com/rdebusscher/microstream-spring-boot-patterns/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249183103,"owners_count":21226142,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-16T16:15:01.936Z","updated_at":"2025-04-16T02:07:10.710Z","avatar_url":"https://github.com/rdebusscher.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MicroStream Spring Boot Usage Patterns\n\nThe **v2** directory contains the examples with Spring Boot 2.x. **v3** is for Spring Boot 3.x\n\n\nPatterns in using MicroStream's Spring Boot integration.\n\n\nThis repository contains a few example projects that demonstrate how MicroStream and\nthe MicroStream Spring Boot integration can be used in a project.\n\nIt is not the idea to have all best practices around application development covered\nin these examples but only some patterns that can be used to work with the Object Graph that\nmakes your database within the JVM heap (and persist data with MicroStream).  \nSo aspects like security and observability are not covered but are also not affected by using MicroStream in your Spring Boot application.\n\n## General concepts\n\nThe following are a few general concepts that are applied within the examples.\n\n- The examples are mainly centered around using REST endpoints to process user requests.\n- The *Controller* classes are responsible for defining the REST endpoint signatures (HTTP method, URLs, ...) and some validation on the received data (structures). So the _Controller_ is not responsible for validating the business rules (does the user exists, can the customer place an order, ...) but merely some validation of the received values/structures (Are values in a certain range like non-negative for age, does the supplied JSON has a property email, ...)\n- The *Repository* classes are responsible for validating the business rules of your application.  They should not have any REST-specific relation so in case there is a problem with a request, a RuntimeException-based exception can be thrown.\n\n## Example\n\nThe example supports (in a limited way) the management of a library.  You have the concept books and users who can lend a book.\n\nYou can perform the following actions\n\n- Retrieve all known books.\n- Retrieve all known users.\n- Add a new User.\n- Update the email address of a User.\n- Retrieve the books assigned to a user.\n- Assign a book to the user.\n\nAlthough the application is still limited in functionality, it is already a bit more elaborated than a hello-world style application. With a hello-world style example, we would have the danger of showing patterns that are not applicable to the real world.\n\n## Foundation example\n\n(See directory _storage-foundation_)\n\nThe MicroStream Spring Boot integration exposes a Spring bean that implements the *EmbeddedStorageFoundation* interface.  The Storage Foundation has used the configuration values that Spring has found in your environment.\n\nWithin the example, the configuration values are placed within the _application.properties_ file (standard properties file for Spring Boot) and can be used to determine the location of the storage on disk, number of channels etc ...\n\nBased on this Storage Foundation, the actual *StorageManager* is started with some additional configuration by the developer. Within the class *DataConfiguration* some examples are given of the customization the developer can make to the _StorgeManager_.  It includes the definition of custom Type handlers and Legacy Type handlers.\n\nThe method *defineStorageManager()* also checks if there is already a _Root_ object available within the Storage Manager. If that is not the case, the _Root_ object needs to be initialised and in this example, some initial data are also created (the database is populated with initial data).\nAlso, the _Root_ object is supplied with the _StorageManager_ so that changes can be stored when they are made to the _Root_ object.\n\nSince we should not return modifiable collections from the _Root_ object, as that would allow for changes to the database by other parts of the application without storing the changes to the external storage, the Root object needs to access the *StorageManager.store()* method for all methods that modify the _database_.\n\nEach Repository Spring bean has a constructor that takes the _StorageManager_ as a parameter.  The _Root_ object is retrieved from this parameter so that the _Repository_ code can delegate actions on the _database_ to the _Root_ object.\n\nHave a look at the file _commands.txt_ for the example of CURL commands that the application support.\n\n# Plain\n\nWithout the integration code, see directory _plain_.\n\nYou can compare the previous example with the code where we do not use the MicroStream Spring Boot integration.  The only difference is in how the configuration values for the _StorageManager_ are retrieved.\n\n\nWithin the class *DataConfiguration* we now create an *EmbeddedStorageFoundation* instance ourselves instead of letting the integration code do this for ourselves.  The configuration is read from a properties file and the location of that file is retrieved from the Spring Boot configuration.  Of course, we might also read the individual configuration values from the Spring Boot configuration just as the integration does.\n\nOther than this, the project code is identical to the previous example.\n\n# Lazily started StorageManager\n\nSee directory _lazy_.\n\nWithin the storage foundation example, the MicroStream Storage manager is started when the Spring Boot application starts.  This is because the bean is also injected into the repositories that are created at boot time.\n\nThere are 2 options to avoid this when you don't want or can't start the _StorageManager_ when the application is started.\n\nUse the Spring Boot configuration parameter to start all Beans lazily.\n\n````\nspring.main.lazy-initialization=true\n````\n\nThis option is put in a comment into the _application.properties_ file of the _storage-foundation_ example so that you can test it out.\n\nIn this case, all Spring beans are only created when they are accessed. In the case of the example, this means that the _StorageManager_ is only started with the first user request.\n\nIf you don't want to have all Beans lazily created, you can make use of the @Inject  Provider class that is supported by Spring.\n\nAn example is created in this _lazy_ program and you can find the following construct in the *UserRepository*:\n\n````\npublic UserRepository(Provider\u003cStorageManager\u003e storageManagerProvider) {\n\n    this.storageManagerProvider = storageManagerProvider;\n}\n\nprivate Root getRoot() {\n    return (Root) storageManagerProvider.get().root();\n}\n````\n\nWe do not use the _StorageManager_ itself in the constructor but ask Spring for an implementation of a _Provider_ that will give us the Bean later on.\n\nWhen we need to access the Root object, we actually ask the _StorageManager_ from the provider and get to the Root object. So only when processing the user request, the StorageManager is initialized (if not done already previously), and it is not started at application startup.\n\n\nDo realise that starting the _StorageManager_ might be required for your use case when not all resources are available at application startup (like a database if you really need to use the database as storage target with MicroStream) it has a performance impact on the first user request as the _Storagemanager_  loads the Root object data at that moment.\n\n# Proposed changes\n\nThe following is the list of proposed changes to make the code (of your application) better structured and integration with MicroStream easier.\n\nThe examples require the code from https://github.com/microstream-one/microstream/pull/390 to compile and work.\n\n# Foundation customizer\n\nSee directory _foundation-customizer_\n\nThe new version of the integration has implemented the following steps so that you can directly use a _StorageManager_ based Spring bean. (and as the developer keep full control of customizations and initializations)\n\n- Build `EmbeddedStorageFoundation` from the Configuration values\n- Allow customizations by the developer `EmbeddedStorageFoundation` using `EmbeddedStorageFoundationCustomizer`\n- Integration creates the `StorageManager`\n- Allow initialization of the `StorageManager` (like adding initial data when storage is created at the first run) through `StorageManagerInitializer`.\n\nThe code within the `DataConfiguration` of the _storage-foundation_ becomes now better structured and results in the classes `FoundationCustomizer` and `RootPreparation`.\n\n```\n@Component\npublic class FoundationCustomizer implements EmbeddedStorageFoundationCustomizer {\n\n    @Override\n    public void customize(EmbeddedStorageFoundation embeddedStorageFoundation) {\n      // Do customization\n    }\n}\n```\n\n```\n@Component\npublic class RootPreparation implements StorageManagerInitializer {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(RootPreparation.class);\n\n    @Override\n    public void initialize(StorageManager storageManager) {\n       // Check if Root is available (and assign if needed) and add initial data if needed.\n    }\n}    \n```\n\nThe addition of these 2 interfaces and using them when the Spring Bean for the _StorageManager_ is created, makes the code better structured as each class has its own purpose.\n\n# Root Spring Bean\n\nSee directory _root-bean_.\n\nWithin the _Repository_ beans, we accessed the Root object through the _StorageManager_ bean.  Although this works, it is a bit cumbersome to always retrieve the root in that way.\n\nWith this updated version of the integration, the Root object can also be turned into a Spring Bean by annotation it with `@Storage`.\n\n```\n@Storage\npublic class Root {\n\n    @Autowired\n    private transient StorageManager storageManager;\n```\n\nThe _storage_ annotation is a custom annotation that is both a `@Component` and `@Qualifier`.  That way it can be detected by the Spring Boot integration and made sure that it is a Spring Bean but also correctly registered with the Storage manager.\n\nSince it is a Spring Bean, you can also inject other beans into it, like the _StorageBean_. Since the integration is responsible for creating the Root object instance if needed (through a special factory method) using standard Java constructs, only Field and Setter injection is allowed (and not constructor injection)\n\n# Multiple Managers\n\nSee directory _multiple-managers_\n\nThis requires version 8.0 (or [this PR](https://github.com/microstream-one/microstream/pull/490))\n\nThere might be situations in that you want to make use of multiple _StorageManager_s. Just like you have applications that talk to multiple databases. This is also possible with MicroStream. And with version 8.0 there will be support within the Spring Boot integration to define multiple Storage Managers that are integrated as Spring Beans.\n\nIf you have multiple beans of the same type, you need to make a distinction between them through the use of _Qualifiers_ in Spring.  Also in case you want to have multiple Storage Managers. Since we cannot know the name of the labels you want to give each StorageManager, you need a little Configuration Bean to configure the beans. But you can make use of a _Provider_ from the integration code so that the amount of code that you need to write is very limited.\n\n```\n@Configuration\npublic class DefineStorageManagers {\n\n    private final StorageManagerProvider provider;\n\n    public DefineStorageManagers(StorageManagerProvider provider) {\n        this.provider = provider;\n    }\n\n    @Bean(destroyMethod = \"shutdown\")\n    @Qualifier(\"green\")\n    public EmbeddedStorageManager getGreenManager() {\n        return provider.get(DatabaseColor.GREEN.getName());\n    }\n\n    @Bean(destroyMethod = \"shutdown\")\n    @Qualifier(\"red\")\n    public EmbeddedStorageManager getRedManager() {\n        return provider.get(DatabaseColor.RED.getName());\n    }\n}\n```\n\nYou can freely choose the name of the class, as long as you annotate it with the `@Configuration`Spring annotation. You need to inject the `StorageManagerProvider` bean that is available through the integration code so that you can call the method `.get(qualifier)` on it to instantiate and expose a `StorageManager`.  The qualifier label and the method parameter should match (for your own ease). Within the code, I made use of an enum but an annotation member doesn't allow it since you can only provide constants.\n\nThe qualifier label is also used as a prefix to look up the configuration values. For the above example, you could have the following entries in the configuration file\n\n```\none.microstream.red.storage-directory=red-db\none.microstream.red.channel-count=2\n\none.microstream.green.storage-directory=green-db\none.microstream.green.channel-count=1\n```\n\nBut of course, all storage types (disk, database, etc ...) are supported, just as we have seen earlier. Don't forget to include also the qualifier label as part of the configuration key as shown in the example.\n\nAlso, the `@Storage` annotation and `StorageManagerInitializer` and `EmbeddedStorageFoundationCustomizer` concepts are supported when you make use of multiple _Storage Managers_.\n\nIn the case of the @Storage annotation, also add the correct `Qualifier` annotation. That way, the integration knows which class it needs t associate with which Storage Manager.\n\n```\n@Storage\n@Qualifier(\"red\")\npublic class Products {\n```\n\nAnd when you have defined a bean that implements `StorageManagerInitializer` and `EmbeddedStorageFoundationCustomizer`, you can find out based on the `databaseName` of the _Foundation_ of the _StorageManager_ which variant is passed in.  An example is\n\n```\n@Component\npublic class RootPreparationOfRedDatabase implements StorageManagerInitializer {\n\n    @Override\n    public void initialize(StorageManager storageManager) {\n        if (!DatabaseColor.RED.getName().equals(storageManager.databaseName())) {\n            // This customizer operates on the Red database\n            return;\n        }\n        // Perform the required initialisation for the Red root = Products\n    }\n}\n```\n\n# Multiple Manager (variation)\n\nSee directory _primary_\n\nInstead of defining 2 _named_ managers, you can also make use of the _primary_ manager that is available and use it when you only need one manager.\n\nThe changes that are required to use this variation with the previous multiple manager's example, are minimal.\n\nYou only have to define one manager within the `DefineStorageManagers` configuration class. For example, you can remove the definition of the Red variant and only keep the Green one (and call it maybe _secondary_.\n\nThis change needs to be reflected in the configuration keys.  The _Primary_ storage manager reads the keys without a label, so we only need to have the `one.microstream.` prefix.  For the secondary, we still need the correct label which is the value of the parameter we use when we call `StorageManagerProvider.get()`.\n\nIf we define a Root object through the `@Storage` annotation, we don't need to specify a Qualifier if it is the root for the primary _StorageManager_. We only need it for additional ones, like the secondary.\n\nAnd when we have implemented classes that implement the `StorageManagerInitializer` or `EmbeddedStorageFoundationCustomizer` interfaces, we must use the _'Primary'_ as the value for the database name to detect if the methods are called for the Primary _StorageManager_.\n\n\n# Dev Mode\n\nSee directory _dev-mode_\n\nWith the changes of https://github.com/microstream-one/microstream/pull/518, it is no longer needed to define the MicroStream jars as part of the _restart classloader_ to have support for reloads.\n\n# Spring Cache integration\n\nSee directory _cache_\n\nMicroStream can be used as Spring Cache provider and the cache values will be persisted so that you have an already filled cache when your application starts. The application within this directory shows an example how you can achieve this.\n\nIn addition to the _microstream-integrations-spring-boot_ artefact, you also need to add the _microstream-cache_ to have the Cache API and MicroStream implementation code available within your project. For Spring Boot, you need the _spring-boot-starter-cache_ artifact.\n\nFollowing configuration steps are needed.\n\nDefine the annotation `@EnableCaching` on the Spring Boot application class, together with the `@SpringBootApplication` annotation.\n\nDefine a Spring Bean that implements the interface `org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer`. You can inject the `EmbeddedStorageManager` that is created by the integration code and configure within the `customize()` method the caches. For each cache you need to specify the name and the Expiry Policy.  Have a look at the `CacheSetup` class.\n\n```\n    private void defineCache(CacheManager cacheManager, String cacheName, Duration duration) {\n        CacheConfiguration\u003c?, ?\u003e configuration = CacheConfiguration\n                .Builder(Object.class, Object.class, cacheName, storageManager)\n                .expiryPolicyFactory(CreatedExpiryPolicy.factoryOf(duration))\n                .build();\n\n        cacheManager.createCache(cacheName, configuration);\n    }\n```\n\nThe built in `SimpleKey`implementation that is used for the cache key is not useable with MicroStream. Its `hashCode` value is calculated when the instance is created and stored in a _transient_ field. This transient value is not persisted by MicroStream and thus all the key values loose their hash value which is used to lookup values. So after a restart of the applications, no keys are detected anymore and it appears that the cache is empty.  To Solve this you need to create a custom key generator.\n\n```\npublic class CustomKeyGenerator implements KeyGenerator {\n    @Override\n    public Object generate(Object target, Method method, Object... params) {\n        // Key should only depend on parameters so that @Cacheable and @CacheEvict annotated methods result in same key\n        return \"Key\" + StringUtils.arrayToDelimitedString(params, \"_\");\n    }\n}\n```\n\nThis key generator is picked up by defining a class like `CacheConfig`\n\n```\n@Configuration\npublic class CacheConfig implements CachingConfigurer {\n\n    public KeyGenerator keyGenerator() {\n        return new CustomKeyGenerator();\n    }\n}\n\n```\n\nThe above steps are needed to make use of the `@Cacheable` and `@CacheEvict` on Spring bean methods to have automatic caching functionality.\n\nPlease note that you can't combine the Cache functionality through MicroStream and using a Root object together. You need different Storage Managers for that purpose. Within version 8.0 of MicroStream, you can define multiple managers more easily.\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frdebusscher%2Fmicrostream-spring-boot-patterns","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frdebusscher%2Fmicrostream-spring-boot-patterns","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frdebusscher%2Fmicrostream-spring-boot-patterns/lists"}