Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide support for auto-configuring multiple beans #15732

Open
wilkinsona opened this issue Jan 17, 2019 · 23 comments
Open

Provide support for auto-configuring multiple beans #15732

wilkinsona opened this issue Jan 17, 2019 · 23 comments
Labels
status: pending-design-work Needs design work before any code can be developed type: enhancement A general enhancement
Milestone

Comments

@wilkinsona
Copy link
Member

wilkinsona commented Jan 17, 2019

Auto-configuration of a single DataSource works well for the vast majority of users, but when a subsequent DataSource is required things get harder (see #7652 for one example) as all of the data sources then need to be manually configured. We'd like to make things easier by providing support for auto-configuring multiple DataSources. In terms of properties, this could look something like this:

spring.datasource.primary.url=…
spring.datasource.primary.username=…
spring.datasource.primary.password=…

spring.datasource.secondary.url=…
spring.datasource.secondary.username=…
spring.datasource.secondary.password=…

Some more design work is needed, but primary could be used as a "special" name that results in the auto-configured bean being marked as @Primary. We'd also need similar functionality for components that consume a DataSource such as JPA, transaction management, Flyway and Liquibase.

@wilkinsona wilkinsona added type: enhancement A general enhancement status: pending-design-work Needs design work before any code can be developed labels Jan 17, 2019
@mbhave
Copy link
Contributor

mbhave commented Jan 17, 2019

We might also need @AutoConfigureTestDatabase#replace to support replacing all auto-configured databases.

@OrangeDog
Copy link
Contributor

With such a property design, I would like the current spring.datasource properties, including vendor-specific ones, to become the defaults for the other ones.

@membersound
Copy link

membersound commented May 13, 2020

Please note that spring.datasource.hikari.* (min/max idle, timeout, etc) and spring.datasource.hikari.data-source-properties.* should then also automatically be applied to any secondary auto-configured datasource. As it's likely the user wants the same config for all databases in the application.

@wilkinsona
Copy link
Member Author

wilkinsona commented Feb 20, 2021

We should keep AbstractRoutingDataSource in mind while working on this. It may be useful to group together two or more auto-configured DataSources into an AbstractRoutingDataSource, and perhaps not even expose the underlying DataSources as beans.

We should also ensure that whatever we come up with isn't just applicable to DataSources. #25369 raised the possibility of configuring multiple RabbitMQ connection factories and we need something that's consistent across all sorts of different data stores, message brokers, etc.

@muhmud
Copy link

muhmud commented Feb 20, 2021

Completely concur with that last point, I have just recently had the same requirement for kafka.

@OrangeDog

This comment has been minimized.

@wilkinsona

This comment has been minimized.

@OrangeDog
Copy link
Contributor

OrangeDog commented Feb 22, 2021

It may be useful to group together two or more auto-configured DataSources into an AbstractRoutingDataSource, and perhaps not even expose the underlying DataSources as beans.

I think that's only useful right now for e.g. multi-tenant systems where you route on e.g. SecurityContext. It's not going to work for e.g. when you have completely different entities in different databases needed at the same time.

In the former case, if there is auto-configuration of multiple DataSources beans, it should be easy to manually gather them into a @Primary AbstractRoutingDataSource with the needed routing logic. The reverse is not true.

@wilkinsona
Copy link
Member Author

To be clear, I wasn't proposing that we'd always automatically create the AbstractRoutingDataSource but that we would provide a property or similar convenience that allows someone to opt in to two or more of the auto-configured DataSources being combined into a routing DataSource. If those DataSources are only ever used via an AbstractRoutingDataSource, this would remove the need for them to be beans.

@OrangeDog
Copy link
Contributor

I'm not sure there's any opinionated way to do that. You could tag each DS with a routing value in its properties, but you still need an implementation to determine what the selected value should be when creating a connection.

@wilkinsona
Copy link
Member Author

wilkinsona commented Feb 22, 2021

Indeed. You'd need something that provides the logic for at least determineCurrentLookupKey(). We could provide a strategy interface for that, but it may not be worth doing so. At this stage, it's really just something to bear in mind as we have some longer-term interest in minimising beans in the context or somehow getting rid of beans that have served their purpose.

@philwebb philwebb modified the milestones: 2.x, 3.x Aug 19, 2022
@wilkinsona wilkinsona changed the title Provide support for auto-configuring multiple datasources Provide support for auto-configuring multiple beans Aug 31, 2022
@wilkinsona
Copy link
Member Author

I've change the issue's title to reflect the fact that this should work for any type of bean and not just for data sources. Once we've figured out the general approach, we can then open additional issues as needed to tackle anything that's DataSource-specific.

When we start working on this, #32194 contains some interesting ideas that we should evaluate and discuss with the Framework team.

@ilkou
Copy link

ilkou commented Apr 30, 2023

Please note that spring.datasource.hikari.* (min/max idle, timeout, etc) and spring.datasource.hikari.data-source-properties.* should then also automatically be applied to any secondary auto-configured datasource. As it's likely the user wants the same config for all databases in the application.

Hi!
I'm doing this for my dynamic datasources configuration

myapp.config:
  # generic configuration
  datasouce:
    hikari:
      minimum-idle: 2
      maximum-pool-size: 10
      idle-timeout: 30000
      max-lifetime: 60000
  liquibase:
    change-log: classpath:db/changelog/db.changelog-master.xml
  tenants:
    - name: master
      datasource:
        url: ${DATABASE_URL}
        username: ${DATABASE_USERNAME}
        password: ${DATABASE_PASSWORD}
        # other configs are inherited from the generic configuration
    - name: client1
      datasource:
        url: ${DATABASE_URL_CLIENT1}
        username: ${DATABASE_USERNAME}
        password: ${DATABASE_PASSWORD}
        # overrides the generic configuration
        hikari:
          minimum-idle: 4
          maximum-pool-size: 10
          idle-timeout: 30000
          max-lifetime: 60000
      liquibase:
        change-log: classpath:db/changelog/db.changelog-client1.xml

it supports both cases that u have mentioned for hikari with a simple

        HikariConfig hikariConfig = customHikariConfig != null ? customHikariConfig : genericHikariConfig;

Still figuring out how to apply liquibase migrations for all datasources, as for now spring boot doesn't support migration of a bean of type AbstractRoutingDataSource Or multi datasources in general

@kanakharaharsh
Copy link

Please refer to my solution here spring-projects/spring-framework#21415

@bekoenig
Copy link

bekoenig commented Oct 22, 2023

Hi @wilkinsona,

what's the status of this topic? There are a lot of ideas and codesnips to support multiple beans, but all are declined or the discussion has stopped.

For flyway we need multiple bean support for different schemas with the same or with different databases. The usecases are motivated by providing shared libs with migrations in classpath, which are fired against isolated environments.

I have two different drafts:

  • dynamical from property with support for annotated beans (javamigration, callback,...)
  • explicit with builder

Both designs are fully compatible with the current behaviour, but after reading the other issues (like #25369) I'am a bit disillusioned to finish the test and the documentation, just for it to get declined as well.

What's the decision from the framework team with this design topic?

Greetings,
Ben

@wilkinsona
Copy link
Member Author

This is something that we'd like to support but we do not yet know how to do so. Some design work is required and, unfortunately, until that has happened, we're unlikely to be in a position to accept a contribution. Perhaps you could share your drafts in their current form by linking to your fork so that we can take what you're trying to do into consideration?

@nightswimmings
Copy link

nightswimmings commented May 3, 2024

@wilkinsona I know we already discussed the multitenancy datasource feature in #28812. But really, 3 years later, I do encourage the lead team to reassess providing such a feature. I think it is the most recurrent most complex feature we need to build over the near perfect Boot, and almost everyone have same needs (you even have a blog post about it https://spring.io/blog/2022/07/31/how-to-integrate-hibernates-multitenant-feature-with-spring-data-jpa-in-a-spring-boot-application).

AbstractRoutingDataSource does not make it trivial to modify the list of datasources in runtime in a thread-safe way, nor has a built-in integration with Hibernate multitenancy gimmicks (so you have to deal with very low-level stuff for the L2 cache to work fine), it does not provide efficient initialization facilities (like async init if you have 2000 datasources, and the fact that the hibernate-recommended use_jdbc_metadata_defaults settings makes you having to manage the datasource map in order to provide the lenientFallback/defaultDatasource arbitrarily), it does not integrate with Flyway. And besides, integrating the determineCurrentLookupKey() with jwt resource-server oauth would be really easy.

I don't think such arch would require a massive rework and it's certainly a killer feature. There's dozens of resources in internet trying to approach this need and a standard way would be amazing. The abstraction of the datsourceconfig source, and the one that deals with tenant mapping to datasource (schema,table,database) would be more time-consuming, but I think it's feasible

@neshkeev
Copy link

neshkeev commented Sep 9, 2024

Please consider the following approach to structure the properties:

myapp.first-datasource.spring.datasource.name=FirstDS
myapp.first-datasource.spring.datasource.url=jdbc:postgresql://first:5432/first
myapp.first-datasource.spring.datasource.username=first
myapp.first-datasource.spring.datasource.password=first

myapp.second-datasource.spring.datasource.name=SecondDS
myapp.second-datasource.spring.datasource.url=jdbc:mysql://second:5432/second
myapp.second-datasource.spring.datasource.username=second
myapp.second-datasource.spring.datasource.password=second

And then a developer defines their own @ConfigurationProperties bean which strips the myapp prefix and the fields of the bean strip the second level prefix by their names up until spring.datasource:

@ConfigurationProperties(prefix="myapp")
public class MyAppProperties {
    private DataSourceProperties firstDatasource;
    private DataSourceProperties secondDatasource;

    // getters + setters
}

Spring Boot should notice that there are @ConfigurationProperties beans embedded into another @ConfigurationProperties bean and auto-configuration kicks in

Please find more info in #42179

@itsmoonrack
Copy link

Hello, is this issue still being discussed ? I would love to have some directions at least on the API/property level so i can implement my own/preview that will be forward compatible

quaff added a commit to quaff/spring-boot that referenced this issue Oct 12, 2024
It's used for configuring multiple beans (spring-projects#15732), we could extract common or use primary properties as parent now.

Take `org.springframework.boot.autoconfigure.data.redis.RedisProperties` for example, given:
```
# primary
spring.data.redis:
  host: 127.0.0.1
  port: 6379

# additional
additional.data.redis:
  port: 6380
```
Then effective properties:
```
additional.data.redis:
  host: 127.0.0.1
  port: 6380
```
should be bound to `additionalRedisProperties`:
```java
	@bean(autowireCandidate = false) // do not back off autoconfigured one
	@ConfigurationProperties(prefix = "additional.data.redis", inheritedPrefix = "spring.data.redis")
	RedisProperties additionalRedisProperties() {
		return new RedisProperties();
	}
```
quaff added a commit to quaff/spring-boot that referenced this issue Oct 12, 2024
It's used for configuring multiple beans (spring-projects#15732), we could extract common or use primary properties as parent now.

Take `org.springframework.boot.autoconfigure.data.redis.RedisProperties` for example, given:
```
# primary
spring.data.redis:
  host: 127.0.0.1
  port: 6379

# additional
additional.data.redis:
  port: 6380
```
Then effective properties:
```
additional.data.redis:
  host: 127.0.0.1
  port: 6380
```
should be bound to `additionalRedisProperties`:
```java
	@bean(autowireCandidate = false) // do not back off autoconfigured one
	@ConfigurationProperties(prefix = "additional.data.redis", inheritedPrefix = "spring.data.redis")
	RedisProperties additionalRedisProperties() {
		return new RedisProperties();
	}
```
@quaff
Copy link
Contributor

quaff commented Oct 12, 2024

Please consider the following approach to structure the properties:

myapp.first-datasource.spring.datasource.name=FirstDS
myapp.first-datasource.spring.datasource.url=jdbc:postgresql://first:5432/first
myapp.first-datasource.spring.datasource.username=first
myapp.first-datasource.spring.datasource.password=first

myapp.second-datasource.spring.datasource.name=SecondDS
myapp.second-datasource.spring.datasource.url=jdbc:mysql://second:5432/second
myapp.second-datasource.spring.datasource.username=second
myapp.second-datasource.spring.datasource.password=second

And then a developer defines their own @ConfigurationProperties bean which strips the myapp prefix and the fields of the bean strip the second level prefix by their names up until spring.datasource:

@ConfigurationProperties(prefix="myapp")
public class MyAppProperties {
    private DataSourceProperties firstDatasource;
    private DataSourceProperties secondDatasource;

    // getters + setters
}

Spring Boot should notice that there are @ConfigurationProperties beans embedded into another @ConfigurationProperties bean and auto-configuration kicks in

Please find more info in #42179

I think #42605 is a better alternative.

quaff added a commit to quaff/spring-boot that referenced this issue Oct 12, 2024
It's used for configuring multiple beans (spring-projects#15732), we could extract common or use primary properties as parent now.

Take `org.springframework.boot.autoconfigure.data.redis.RedisProperties` for example, given:
```
# primary
spring.data.redis:
  host: 127.0.0.1
  port: 6379

# additional
additional.data.redis:
  port: 6380
```
Then effective properties:
```
additional.data.redis:
  host: 127.0.0.1
  port: 6380
```
should be bound to `additionalRedisProperties`:
```java
	@bean(autowireCandidate = false) // do not back off autoconfigured one
	@ConfigurationProperties(prefix = "additional.data.redis", inheritedPrefix = "spring.data.redis")
	RedisProperties additionalRedisProperties() {
		return new RedisProperties();
	}
```
quaff added a commit to quaff/spring-boot that referenced this issue Oct 12, 2024
It's used for configuring multiple beans (spring-projects#15732), we could extract common or use primary properties as parent now.

Take `org.springframework.boot.autoconfigure.data.redis.RedisProperties` for example, given:
```
# primary
spring.data.redis:
  host: 127.0.0.1
  port: 6379

# additional
additional.data.redis:
  port: 6380
```
Then effective properties:
```
additional.data.redis:
  host: 127.0.0.1
  port: 6380
```
should be bound to `additionalRedisProperties`:
```java
	@bean(autowireCandidate = false) // do not back off autoconfigured one
	@ConfigurationProperties(prefix = "additional.data.redis", inheritedPrefix = "spring.data.redis")
	RedisProperties additionalRedisProperties() {
		return new RedisProperties();
	}
```
@quaff
Copy link
Contributor

quaff commented Oct 12, 2024

Here is my prototype to support configuring multiple @ConfigurationProperties beans, additional beans properties could inherit primary properties then override specified properties.

Take org.springframework.boot.autoconfigure.data.redis.RedisProperties for example, given:

# primary
spring.data.redis:
  host: 127.0.0.1
  port: 6379
  
# additional
additional.data.redis:
  port: 6380

Then effective properties:

additional.data.redis:
  host: 127.0.0.1
  port: 6380

should be bound to additionalRedisProperties:

	@Bean(autowireCandidate = false) // do not back off autoconfigured one
	@ConfigurationProperties(prefix = "additional.data.redis", inheritedPrefix = "spring.data.redis")
	RedisProperties additionalRedisProperties() {
		return new RedisProperties();
	}

Please note @Bean(autowireCandidate = false) will not back off autoconfigured RedisProperties, it's feature of Spring Boot 3.4.

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Nov 25, 2024

This should be already known as per #31337 (comment), but I want to mention this is relevant for the work on auto-config for HTTP interface clients. There is some relevant work in httpexchange-spring-boot-starter where you can see a configuration model for multiple clients along with a more extensive example. Note there are common properties at the top, and more specific per client property sets below (under channels). However this ends up looking, the concept of common defaults seems useful.

@ChildrenGreens
Copy link

@wilkinsona I previously designed a Redis multi-datasource Spring Boot Starter, which may not be perfect. Recently, during my free time, I decided to open-source it. Here’s the link: https://github.com/ChildrenGreens/multi-source-spring-boot-starter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: pending-design-work Needs design work before any code can be developed type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests