Spring boot jpa multitenancy with dynamic datasources

I'm trying to create a multi tenant web application, and found a good tutorial here. This explains me how to configure the MVC to find the new tenant (with CurrentTenantIdentifierResolver and a MultiTenancyInterceptor that extends HandlerInterceptorAdapter), how to configure three different datasources for three different tenants, and how to give the correct datasource to the server at runtime by extending AbstractDataSourceBasedMultiTenantConnectionProviderImpl

Now, this has been a great starting point to make me understand how multi tenancy in spring and hibernate work, but I want to push this further and I'd like to make it so that the tenants are completely dynamic, i.e. I don't make assumptions on how many tenants there can be for an application.

This is what I thought:

  • The application is configured to scan a path (not in the classpath, e.g. /usr/data/config) at boot, and finds various application.properties files under various directories (one directory for each tenant) e.g. tenantA, tenantB, tenantC...
  • For each application.properties Spring boot will create a datasource based on that file (that file will only have the boot property spring.datasource.url). Note that it would be great to use spring boot's property since it gives me all the information I need from the single URL, like JDBC class and such.
  • I will register each of those datasources in a HashMap (as shown in the previous link)

After that, the basic multitenancy structure is already described in the link aforementioned: everytime an end user makes a request to the browser, the server will elaborate the tenant and give the correct datasource back to find the database that is to be used.

Anyone can point me to some resources, if this has been previously made (I googled a lot but nothing gets me started), or give some advice on which spring classes/configurations to use to achieve this?

Thanks in advance


Solution 1:

That's what I ended up doing, if anyone ever has this need. Any further expansion to this, or comments about best practices infringement will be most welcome.

The DataSourceProvider that extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl has to override two methods

  • selectAnyDataSource that returns back an @Autowired DataSource that is instantiated by Spring with the usual method of instantiating a datasource for an application.
  • selectDataSource(String tenant) does the following:
    • gets the path of the tenant's config folder
    • Instantiates a DataSourceProperties with properties taken from the application.properties file that is found in the tenant's config folder
    • Creates and returns a new DataSource via a DataSourceBuilder, using as properties the fields in the previously instantiated DataSourceProperties (useful because Spring gives you the Driver Class Name dynamically from the database URL)

Code provided here, feel free to use it:

String configPath = [...]; // Instantiate your configuration path
File file = new File(realPath);
DataSourceProperties dsProp = new DataSourceProperties();
Properties properties = new Properties();
try {
    properties.load(new FileInputStream(file));
} catch (IOException e) {
    throw new TenantNotConfiguredException(tenant); // Custom exception
}

PropertiesConfigurationFactory<DataSourceProperties> pcf = new PropertiesConfigurationFactory<>(dsProp);
pcf.setTargetName(DataSourceProperties.PREFIX);
pcf.setProperties(properties);

try {
    dsProp = pcf.getObject();
} catch (Exception e) {
    e.printStackTrace();
}

return DataSourceBuilder.create()
            .url(dsProp.getUrl())
            .driverClassName(dsProp.getDriverClassName())
            .username(dsProp.getUsername())
            .password(dsProp.getPassword())
            .build();