Spring Reactive Multi MongoTemplate Configuration
By: Visweshwar Ganesh
Tags: Spring Webflux, Spring Boot, Spring Mongo Db, Spring, Mongodb

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