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)