Intro
If you're working with Modular Monoliths on Spring Boot, you want to separate your modules into separate databases.
That's exactly my case, and trying to do that was a really pain in the ass. Mainly because all solutions that I've found still didn't make auto index creation work.
One important information: Here I'm using Kotlin with Spring Boot 3.2.
1. Adding Properties
First, you have to configure your application.yml
to set the database names. Mine I decided to configure this way:
# application.yml spring: data: mongodb: uri: mongodb://localhost:27017 first-db: database: first-db second-db: database: second-db
2. Creating Mongo Config for Each Module
Since I'm using a modular monolith, I want to separate Mongo configurations for each module. So repositories in each module can use different databases.
// first/config/FirstMongoConfig.kt @EnableMongoRepositories( basePackages = ["com.example.first"], mongoTemplateRef = "firstMongoTemplate" ) @Configuration class FirstMongoConfig( @Value("\${spring.data.mongodb.first.database}") val dbName: String ) { @Bean(name = ["firstMongoTemplate"]) fun firstMongoTemplate( @Qualifier("firstMongoDatabaseFactory") factory: MongoDatabaseFactory, ): MongoTemplate { return MongoTemplate(factory) } @Bean(name = ["firstMongoDatabaseFactory"]) fun firstMongoDatabaseFactory( @Qualifier("firstMongoClient") mongoClient: MongoClient ): MongoDatabaseFactory { return SimpleMongoClientDatabaseFactory(mongoClient, dbName) } @Bean(name = ["firstMongoClient"]) fun firstMongoClient( mongoProperties: MongoProperties ): MongoClient { return MongoClients.create( MongoClientSettings .builder() .applyConnectionString( ConnectionString(mongoProperties.uri) ) .build() ) } }
Do exactly the same for the second module:
// second/config/SecondMongoConfig.kt @EnableMongoRepositories( basePackages = ["com.example.second"], mongoTemplateRef = "secondMongoTemplate" ) @Configuration class SecondMongoConfig( @Value("\${spring.data.mongodb.second.database}") val dbName: String ) { @Bean(name = ["secondMongoTemplate"]) fun secondMongoTemplate( @Qualifier("secondMongoDatabaseFactory") factory: MongoDatabaseFactory, ): MongoTemplate { return MongoTemplate(factory) } @Bean(name = ["secondMongoDatabaseFactory"]) fun secondMongoDatabaseFactory( @Qualifier("secondMongoClient") mongoClient: MongoClient ): MongoDatabaseFactory { return SimpleMongoClientDatabaseFactory(mongoClient, dbName) } @Bean(name = ["secondMongoClient"]) fun secondMongoClient( mongoProperties: MongoProperties ): MongoClient { return MongoClients.create( MongoClientSettings .builder() .applyConnectionString( ConnectionString(mongoProperties.uri) ) .build() ) } }
3. Enabling Auto Index Creation
This way that we've done, still don't auto create indexes. I was trying to do this multiple ways and checking Spring Data MongoDB documentation I decided to use IndexOperations
to solve this problem.
I simply wrote a component that gets all MongoTemplates
and creates indexes for each one:
// config/BuildMongoIndexes.kt @Component class BuildMongoIndexes( private val mongoTemplates: List<MongoTemplate> ) { @EventListener(ContextRefreshedEvent::class) fun initIndicesAfterStartup() { mongoTemplates.forEach { initIndices(it) } } private fun initIndices(mongoTemplate: MongoTemplate) { val mappingContext = mongoTemplate.converter.mappingContext val resolver = MongoPersistentEntityIndexResolver(mappingContext) mappingContext.persistentEntities .stream() .filter { it.isAnnotationPresent(Document::class.java) } .forEach { val indexOps = mongoTemplate.indexOps(it.type) resolver.resolveIndexFor(it.type).forEach(indexOps::ensureIndex) } } }
4. How to Test
Creating Testcontainers Config
To be able to test for multiple databases, I recommend you using Testcontainers. That's my configuration to start the container:
// test/config/MongoTestConfiguration.kt @TestConfiguration class MongoTestConfiguration { companion object { private val container: MongoDBContainer = startContainer() private fun startContainer(): MongoDBContainer { val container = MongoDBContainer(DockerImageName.parse("mongo:6")) container.start() return container } } @Primary @Bean fun mongoProperties(): MongoProperties { val connectionString = "mongodb://${container.host}:${container.firstMappedPort}" return MongoProperties().apply { uri = connectionString } } }
This will set the MongoProperties
uri
. That will enable the MongoConfig
files to get this URL and connect to MongoDB on tests.
Using on Tests
// test/ExampleTest.kt @SpringBootTest @Import(MongoTestConfiguration::class) class ExampleTest { @Autowired lateinit var exampleRepository: ExampleRepository @Test fun `example test`() { // Here you can normally use your repository exampleRepository.save(Example()) } }
Conclusion
That's all! Now you have multiple databases configurations with automatically index creation and as extra can use these configurations on your tests! \o/
Top comments (0)