Multiple MongoDB Databases on Spring Boot
Tags: spring, kotlin, mongodb, multiple
Published: 2024-04-12 15:02:25

---
title: Multiple MongoDB Databases on Spring Boot
published: true
description: How to configure multiple MongoDB databases using Spring Boot and make auto index creation work correctly
tags: spring, kotlin, mongodb, multiple
cover_image: https://images.unsplash.com/photo-1544197150-b99a580bb7a8?q=80&w=5940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D
# Use a ratio of 100:42 for best results.
# published_at: 2024-04-12 14:20 +0000
---

## 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:

```yml
# 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.

```kotlin
// 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:

```kotlin
// 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](https://docs.spring.io/spring-data/mongodb/reference/mongodb/mapping/mapping-index-management.html) I decided to use `IndexOperations` to solve this problem.

I simply wrote a component that gets all `MongoTemplates` and creates indexes for each one:

```kotlin
// 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](https://testcontainers.com/). That's my configuration to start the container:

```kotlin
// 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

```kotlin
// 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/