-
Notifications
You must be signed in to change notification settings - Fork 41.7k
Description
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.
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 JakartaConnectionFactory - 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
- fems
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 jarPretty 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.