The problem/context
The application I am currently working on is a single-tenant application, with a number of instances, that needs to connect to multiple data sources and JMS servers. Let's start by analysing the documentation on how to configure multiple data sources. There I see a main limitation: in the examples provided, you bind yourself to a data source and don't take advantage of the full magic of the Spring autoconfiguration for DataSource, which includes some magic to choose the pool by configuration, including falling back to H2 database when no configuration is provided. I have used the example above in my application for the multi-DS problem and resulted in the application working, but for example if the app is using Oracle and Postgres, I can't use UCP by means of configuration-only (I would have to use a profile...).
Before going on with my JMS issue, Spring autoconfiguration uses a good combination of @Conditional expressions to build beans of known name and type based on configuration parsing. E.g. you can change the pool type, in our example, by changing a property only, which may even mean you don't have to rebuild the container of the application.
This magic only works if you use a single data source and allow Spring to auto-configure it for you. If you want to configure a myDataSource under spring.my-datasource property namespace, you should copy all classes into your own project and, for example, rename every property reference.
https://github.com/spring-projects/spring-boot/blob/70fd788e1bff98d0cd64e6dc47b1e930c96b910b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java#L198-L106
In the above example, in your own copy of DataSourceConfiguration.java, you would use @ConditionalOnProperty(name = "spring.my-datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource" and then you switch your second datasource to Hikari.
But you have to copy the soruce code of Spring in your own application (I see a potential "derived work" effect here), and maintain it.
With JMS, things get more complicated
My requirement is the following:
- For JUnit tests and when running the application on localhost, use an Artemis Embedded server to simulate JMS for every JMS connection
- For certain known countries (e.g. profiles country_us, country_ca) you need a single IBM MQ server when running in any environment, and its corresponding Jakarta ConnectionFactory
- For other known countries (e.g. profiles country_fr, country_es) you need two named connection factories, one connecting to RedHat AMQ and another to IBM MQ
JMS autoconfigurations define both connection and pooling/caching, driven by configuration properties.
So, I need to define two named connection factories, but the exact implementation depends on the profile factors. I ended up copying code from Spring Boot and IBM MQ jar.
I ended up in 6 packages
- com.acme.autoconfiguration
- fems
- activemq # A copy of Spring ActiveMQAutoConfiguration&friends into the project
- artemis # A copy of Spring ArtemisAutoConfiguration&friends into the project
- ibm # A decompilation of MQAutoConfiguration&friends from IBM jar into the project
- core
- activemq # A copy of Spring ActiveMQAutoConfiguration&friends into the project
- artemis # A copy of Spring ArtemisAutoConfiguration&friends into the project
- ibm # A decompilation of MQAutoConfiguration&friends from IBM jar into the project
The packages contain definitions for @AutoConfiguration classes that read @ConfigurationProperties such as:
spring.activemq-femsspring.activemq-corespring.artemis-femsspring.artemis-coreibm.mq-femsibm.mq-core
And they define a bean of type ConnectionFactory either named femsConnectionFactory or coreConnectionFactory to be used in the code
AutoConfiguration-Factories in help
My idea is to introduce a new mechanism into Spring that declaratively imports a (supported) AutoConfiguration facility by customizing names, in order to be reused and even repeated.
This is to be discussed and expanded, of course, but let me provide an example for the data source case:
@SpringBootApplication
@AutoConfigureDataSource(name="ds1", prefix="spring.ds1") //e.g. spring.ds1.hikari.xxx=yyy
@AutoConfigureDataSource(name="ds2", prefix="spring.ds2") //e.g. spring.ds2.tomcat.xxx=yyy
@EnableJpaRepositories(basePackages="com.myapp.ns1", dataSource="ds1")
@EnableJpaRepositories(basePackages="com.myapp.ns2", dataSource="ds2")
public class MyApplication {
}
And then...
@AutoConfigureArtemis(connectionFactoryName="mainJms", prefix="spring.artemis-main")
@AutoConfigureIbmMq(connectionFactoryName="mainJms", prefix="com.ibm.mq-main") //This is maintained in an IBM jar
Pretty self-explanatory: you will define a DataSource of name ds1, reading from the configuration prefix spring.ds1
The idea sounds ambitious to me too, but I would like to put it under discussion. Thanks for your time.
Comment From: scottfrederick
Thanks for getting in touch. I think the comment here applies to this issue as well. This is a desire that we are aware of and would like to address, but it is potentially a very big change to Spring Boot that will take some time to design and implement and we haven't gotten to that yet. I will close this issue as a duplicate because I believe the existing issues cover the requirements. GitHub will link the issues together so we won't lose your input.