Migration with Multitenant (DATABASE) version:5.0.0-SNAPSHOT
- Dominant language
- Groovy
- Stars
- 2.9k
- Forks
- 975
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 92
Description
Hello!
When activating a MULTITENANT (DATABASE) project, where the configuration is as follows:
SO:
- **Operating System**: Linux Fedora 39
- **Grails Version:** Grails: 6.2.0
- **Plugin Version:** 5.0.0-SNAPSHOT (Downloaded on: 2024-08-12)
- **Database:** Postgresql12
- **JDK Version:** openjdk version "17.0.12" 2024-07-16
- ** LINK TO REPOSITORY **: https://github.com/pinedu1/pndImo
Where I downloaded the source code from the 5.x branch, compiled it, and distributed it to my local Maven repository:
/*code*/
#> ./gradlew publishToMavenLocal -x groovydoc
/*code*/
I added this compilation to my project: build.gradle
/*code*/
buildscript {
dependencies {
classpath 'org.grails.plugins:database-migration:5.0.0-SNAPSHOT'
}
}
sourceSets {
main {
resources {
srcDir 'grails-app/migrations'
}
}
}
...
dependencies {
...
implementation('org.grails.plugins:database-migration:5.0.0-SNAPSHOT')
}
/*code*/
tenantresolver: org.grails.datastore.mapping.multitenancy.TenantResolver (SUBDOMAIN)
application.properties:
# Configurações da Aplicação
grails.gorm.reactor.events=false
grails.gorm.multiTenancy.mode=DATABASE
grails.gorm.multiTenancy.tenantResolverClass=org.grails.datastore.mapping.multitenancy.TenantResolver (SUBDOMAIN)
grails.plugin.databasemigration.updateAllOnStart=true
grails.plugin.databasemigration.updateOnStartFileName=changelog.groovy
grails.plugin.databasemigration.changelogFileName=changelog.groovy
# Default
dataSource.url=jdbc:postgresql://localhost:5432/pnd
dataSource.driverClassName=org.postgresql.Driver
dataSource.username=pnd
dataSource.password=pnd
dataSource.pooled=true
dataSource.jmxExport=true
dataSource.dbCreate=none
# Cliente: joao
dataSources.JOAO.dbCreate=none
dataSources.JOAO.password=joao
dataSources.JOAO.url=jdbc:postgresql://localhost:5432/joao
dataSources.JOAO.username=joao
# Cliente: pedro
dataSources.PEDRO.dbCreate=none
dataSources.PEDRO.password=pedro
dataSources.PEDRO.url=jdbc:postgresql://localhost:5432/pedro
dataSources.PEDRO.username=pedro
# Cliente: antonio
dataSources.ANTONIO.dbCreate=none
dataSources.ANTONIO.password=antonio
dataSources.ANTONIO.url=jdbc:postgresql://localhost:5432/antonio
dataSources.ANTONIO.username=antonio
# Cliente: jose
dataSources.JOSE.dbCreate=none
dataSources.JOSE.password=jose
dataSources.JOSE.url=jdbc:postgresql://localhost:5432/jose
dataSources.JOSE.username=jose
It turns out that in this configuration, the MULTITENANT setup creates the DATASOURCE_NAME map as follows:
datasources.JOAO=[map of properties]
datasources.PEDRO=[map of properties]
...
datasources.ANTONIO=[map of properties]
These dataSources are collected by the plugin in:
class: DatabaseMigrationGrailsPlugin
And in line 100, there is the following method:
/* code */
private Set getDataSourceNames() {
def dataSources = config.getProperty('dataSources', Map, [:])
if (!dataSources) {
return ['dataSource']
}
Set dataSourceNames = dataSources.keySet()
if (!dataSourceNames.contains('dataSource')) {
dataSourceNames = ['dataSource'] + dataSourceNames
}
return dataSourceNames
}
/* code */
In this method, the if block:
/*code*/
if (!dataSourceNames.contains('dataSource')) {
dataSourceNames = ['dataSource'] + dataSourceNames
}
/*code*/
cannot exist in this configuration, as it creates a false entry in the dataSources set and breaks the UPDATE_ALL_ON_START functionality.
In my solution, I simply removed this piece of code (the if condition above).
A second point related to my configuration:
The class DatabaseMigrationTransactionManager also collects the databases to retrieve the TransactionManager linked to each of these tenants.
In line 27 of this class:
/*code*/
PlatformTransactionManager getTransactionManager() {
String dataSource = this.dataSource ?: "dataSource"
String beanName = "transactionManager"
if (dataSource != "dataSource") {
beanName += "_${dataSource}"
}
applicationContext.getBean(beanName, PlatformTransactionManager)
}
/*code*/
The method retrieves the transaction managers, and in my configuration, it retrieves by the object's name:
"transactionManager_JOAO"
"transactionManager_PEDRO"
...
"transactionManager_ANTONIO"
However, at some point, the dataSource name becomes degenerated into the following form:
"dataSource_JOAO"
"dataSource_PEDRO"
...
"dataSource_ANTONIO"
Where it should simply be:
"JOAO"
"PEDRO"
...
"ANTONIO"
My homemade solution was to modify the method as follows:
/*code*/
PlatformTransactionManager getTransactionManager() {
String dataSource = this.dataSource ?: "dataSource"
String beanName = "transactionManager"
if (dataSource.startsWith('dataSource_')) {
dataSource = dataSource.substring(dataSource.indexOf('_') + 1)
}
if (dataSource != "dataSource") {
beanName += "_${dataSource}"
}
applicationContext.getBean(beanName, PlatformTransactionManager)
}
/*code*/
Note that I force the elimination of the 'dataSource_' substring in the following if block:
/*code*/
if (dataSource.startsWith('dataSource_')) {
dataSource = dataSource.substring(dataSource.indexOf('_') + 1)
}
/*code*/
Contributor guide
Research direction
Start with DatabaseMigrationGrailsPlugin.getDataSourceNames and DatabaseMigrationTransactionManager.getTransactionManager, using the reported Grails multitenant PostgreSQL configuration as the reproduction case. Verify that the datasource set contains only intended entries and that transaction managers resolve to the tenant names, then confirm updateAllOnStart works across the configured databases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- groovy, postgresql
- Domain
- backend, databases
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100