DEV Community

Cover image for Multiple MongoDB Databases on Spring Boot
Joao Kersul
Joao Kersul

Posted on

Multiple MongoDB Databases on Spring Boot

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
Enter fullscreen mode Exit fullscreen mode

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()
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
            }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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())
  }
}
Enter fullscreen mode Exit fullscreen mode

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)