Spring Reactive Multi MongoTemplate Configuration
Configuring your application with Spring Data and MongoDB is quite well documented on the internet. Be it the Spring Data documentation or the great starter projects from baeldung.com.
What I was missing was a reference example for a multi mongoTemplate configuration.
Why Multi MongoTemplate Configuration?
There are instances where your application interacts with more than just one instance of a mongoDB database. When you get into this state, the out of the box configurations fail to recognize this state.
What do we need to solve?
There are a few unique issues which you’d experience when you encounter this condition.
- How do we define our Database Properties?
- How do we does the repository differentiate which Database to interact with?
- How do we configure DB options?
Let’s go about answering these questions using a very simple command line example.
Defining MongoDB Properties
To define the properties in this case we break away from the “traditional” spring parameters.
Here is a sample Configuration.
application.yml
spring:
data:
mongodb:
coffee:
database: coffee
uri: mongodb://localhost:27017
burger:
database: burger
uri: mongodb://localhost:27017
autoconfigure:
exclude: org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration
We have two databases coffee
and burger
these could represent the same replicaset or different replicaset. In this case they refer to the same replicaset.
Similar to the Non Reactive Mongo Autoconfiguration, we have exclude the autoconfiguration as we are going to take it over.
Reading the Config file
There is nothing unusual here, this is the general way we reach the property files. Note the ConfigurationProperty Prefix.
package com.acme.multimongo.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "spring.data.mongodb")
@Getter
@Setter
public class CustomMongoProperties {
private MongoProperties coffee;
private MongoProperties burger;
}
Define The Configuration
To define the configuration there are 2 parts
- MongoConfig
- MongoTemplate
- MongoClient
Defining the Mongo Config
Now we have to define a Config for each Database
package com.acme.multimongo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
@Configuration
@EnableReactiveMongoRepositories(basePackages = "com.acme.multimongo.repository.burger",
reactiveMongoTemplateRef = "mongoTemplateBurger")
public class BurgerMongoConfig {
}
NOTE: We have Separated the Annotation @EnableReactiveMongoRepositories
by defining basePackage
and a reactiveMongoTemplateRef
(We’ll see the reactiveMongoTemplateRef
in the next section)
package com.acme.multimongo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
@Configuration
@EnableReactiveMongoRepositories(basePackages = "com.acme.multimongo.repository.coffee",
reactiveMongoTemplateRef = "mongoTemplateCoffee")
public class CoffeeMongoConfig {
}
Defining MongoClient and MongoTemplates
Let’s define the configuration for the Coffee Client
As you can see below we have defined the MongoClient, where we inject the ClientSettings and then inject the MongoClient into the template.
NOTE: We have to define any one of the MongoTemplate as @Primary
.
@Primary
@Bean
public MongoClient reactiveMongoClientCoffee() {
return MongoClients.create(createMongoClientSettings(customMongoProperties.getCoffee()));
}@Primary
@Bean("mongoTemplateCoffee")
public ReactiveMongoTemplate reactiveMongoTemplateCoffee(){
return new ReactiveMongoTemplate(reactiveMongoClientCoffee(),customMongoProperties.getCoffee().getDatabase());
}
private MongoClientSettings createMongoClientSettings(MongoProperties mongoProperties){
ConnectionString ConnectionString = new ConnectionString(mongoProperties.getUri());
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.readConcern(ReadConcern.DEFAULT)
.writeConcern(WriteConcern.MAJORITY)
.readPreference(ReadPreference.primary())
.applyConnectionString(ConnectionString)
.build();
return mongoClientSettings;
}
Similarly for the burger client. Putting it now all together
package com.acme.multimongo.config;
import com.mongodb.*;
import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
@Configuration
public class MongoConfiguration {
private final CustomMongoProperties customMongoProperties;
public MongoConfiguration(CustomMongoProperties customMongoProperties) {
this.customMongoProperties = customMongoProperties;
}
@Primary
@Bean
public MongoClient reactiveMongoClientCoffee() {
return MongoClients.create(createMongoClientSettings(customMongoProperties.getCoffee()));
}
@Bean
public MongoClient reactiveMongoClientBurger() {
return MongoClients.create(createMongoClientSettings(customMongoProperties.getBurger()));
}
@Primary
@Bean("mongoTemplateCoffee")
public ReactiveMongoTemplate reactiveMongoTemplateCoffee(){
return new ReactiveMongoTemplate(reactiveMongoClientCoffee(),customMongoProperties.getCoffee().getDatabase());
}
@Bean("mongoTemplateBurger")
public ReactiveMongoTemplate reactiveMongoTemplateBurger(){
return new ReactiveMongoTemplate(reactiveMongoClientBurger(),customMongoProperties.getBurger().getDatabase());
}
private MongoClientSettings createMongoClientSettings(MongoProperties mongoProperties){
ConnectionString ConnectionString = new ConnectionString(mongoProperties.getUri());
MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
.readConcern(ReadConcern.DEFAULT)
.writeConcern(WriteConcern.MAJORITY)
.readPreference(ReadPreference.primary())
.applyConnectionString(ConnectionString)
.build();
return mongoClientSettings;
}
}
Using these Configuration in a simple class
Details on how MongoDB and Reactive work are beyond the scope of this document. It’s worth referring to documentation for that purpose.
package com.acme.multimongo;
import com.acme.multimongo.repository.burger.BurgerStoreRepository;
import com.acme.multimongo.repository.burger.StoreEntity;
import com.acme.multimongo.repository.coffee.CoffeeShopRepository;
import com.acme.multimongo.repository.coffee.ShopEntity;
import java.util.List;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.reactive.config.EnableWebFlux;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@SpringBootApplication
@EnableWebFlux
public class MultimongoApplication implements CommandLineRunner {
@Autowired
private BurgerStoreRepository burgerStoreRepository;
@Autowired
private CoffeeShopRepository coffeeShopRepository;
public static void main(String[] args) {
SpringApplication.run(MultimongoApplication.class, args);
}
@Override
public void run(String ... args) {
Mono<List<StoreEntity>> storeList = this.testStore();
Mono<List<ShopEntity>> cafeList = this.testCafe();
storeList.then(cafeList).subscribe();
}
protected Mono<List<StoreEntity>> testStore(){
Mono storeDeleteMono = this.burgerStoreRepository.deleteAll();
Mono<List<StoreEntity>> rsp = storeDeleteMono
.thenMany(
Flux.just("BK","MCD","FG")
.map(name -> new StoreEntity(name,new Random().nextInt()%10))
.flatMap(burgerStoreRepository::save)
).thenMany(burgerStoreRepository.findAll())
.collectList();
return rsp;
}
protected Mono<List<ShopEntity>> testCafe(){
Mono shopDeleteMono = this.coffeeShopRepository.deleteAll();
return shopDeleteMono
.thenMany(
Flux.just("SB","DD","TH")
.map(name -> new ShopEntity(name,new Random().nextInt()%10))
.flatMap(coffeeShopRepository::save)
).thenMany(coffeeShopRepository.findAll())
.collectList();
}
}
As always the code is available on Github
References
Spring Data Documentation for MongoDB — https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#reference
Spring Reactive Configuration for MongoDB — https://www.baeldung.com/spring-data-mongodb-reactive