Requirement: There is a software product that can be sold to different customers, but the provider wants to keep the sensible data of the customer in separate data sources. Every customer/login has only access to exactly one data source.
In a, let’s say, ‘simple’ Spring setup, one would simply add all databases to the configuration type of his choice. Spring currently offers four different ways of container configuration:
If you are interested in the details about the different possibilities, I refer to the Spring Reference Manual.
Customers get a pile of logins which are associated to a specific customer key, which should delegate to the correct database. There is always only one database relevant per customer.
Quick as a shot, the experienced Spring developer will refer to the ‘AbstractRoutingDataSource‘ which stores a Map of data sources internally. Based on your implementation of determineCurrentLookupKey() – typically a ThreadLocal value is accessed – the correct data source from the internal map is chosen for opening a connection. The Spring Security experts will quickly show you how to introduce the customer key to your current session which you can easily access in your AbstractRoutingDataSource. It looks even more powerful in combination with Jndi, because it provides a look-up mechanism to search for JndiResources based on names.
All that sounds nice apparently. But this approach has two drawbacks for us:
For now, lets forget about these problems. We want to use the AbstractRoutingDataSource but without Jndi lookup functionality and don’t care about Hibernate performance ;).
Initially, when the application was rolled out for the first time, there was only one customer present. Apart from that, a demo customer was created to prove the multiple-customer feature. No one was really complaining about the fact that a developer needs to insert the data source configuration for every new customer. Everything looked good, until the day came, where a second customer was contracted. First of all we should set up a test environment, with a separate data source. Then the real one. Temporarily we thought about having a fifth ‘customer’ for running various integration tests. The team finally recognized, that the task of setting up the data source beans should be pushed to OPS somehow, because they already have to define the correct JndiResource in the servlet context.
New developer-driven requirement: Setting up a new customer is an OPS task, hence, introducing a new data source should not affect the development team, because there is nothing to develop!
A first brainstorming session brought up only one obvious idea: We need to partly expose our Spring XML config. “With great power comes great responsibility” and to be honest, no one really wants to share the power of configuring a Spring container.
After a short recherche session, we found many questions dealing with the same sort of topic: “How to dynamically create spring beans based on some sort of configuration provided without rebuilding the war file?”
Our approach with the ‘AbstractRoutingDataSource’ was referenced sometimes which confirmed our initial approach. But those especially arose my attention:
My idea was to have our very own Spring Bean definition approach which does not affect the war file! I immediately started to search for bean definition alternatives and also stumbled upon the Spring JdbcBeanDefinitionReader and the above mentionedPropertiesBeanDefinitionReader. Both classes helped me understanding how Spring internally collects all the different bean definition details before initializing the beans. (A good resource for understanding, how a Spring ApplicationContext gets initialized is this SpringONE presentation.) I got temporarily discouraged, as I saw, that I can’t simply add a BeanDefinitionReader implementation somewhere, to an ApplicationContext. It felt wrong to implement my own ApplicationContext so I had one last chance. The fourth link provides an approach how to programatically create bean definitions with a BeanFactoryPostProcessor (BFPP) implementation. When you first read the javadoc it sounds a bit like the wrong way, even the bean name itself does not really imply to create bean definitions. Second thing is, that BFPP implementations assume, that all bean definitions are already in place, so the order of execution would become essential. But we are lucky, “there is some Spring for it” of course ;). In Spring 3.0.1 an interface extension to BFPP was introduced, the BeanDefinitionRegistryPostProcessor (BDRPP) (I love these names :)).
Manu Cornet – Bonkers World Blog
I decided to go for it and confined myself for a prototyping weekend.
This is the list of acceptance criteria I defined:
There were many pitfalls an dead-ends coming around this weekend. That’s why I prefer to show you the solution and explain why I did it like that.
Well not the common way. Because BeanDefinitionRegistryPostProcessors are instantiated in a very early stage of the initialization process, you can’t use functionality like @Autowired or@Value. The technical reason can be looked up in the SpringONE presentation. Now the only things you can do:
A short explanation, why the Spring Environment is helpful here: Since 3.1 Spring separates the configuration from the ApplicationContext, which means, that stuff like defined config properties and activated spring profiles got their own interfaces. So it is good practice to initialize your Environment before a single bean is instantiated or even defined. There are many conventions, which setup your environment automatically, but the true power comes along if you do it on your own. But that’s a topic for a new post. For the impatient here are some good resources to get a first impression:
Now, this is what our BeanDefinitionRegistryPostProcessor now looks like:
Now Spring offers again different possibilities how to actually add a custom BeanFactoryPostProcessor / BeanDefinitionRegistryPostProcessor to the set of processors executed:
In the likely case of developing a web application, please don’t forget to define the initializer in you web.xml:
If you are lucky and OPS offers you a Servlet 3.0+ environment, the programmatic approach should be self-explanatory.
Looks useful? Thanks :).
The last thing missing is the initialization of the AbstractRoutingDataSource. And there it gets a bit bumpy. What we now have, is a set of factory beans which do a jndi lookup for adding those DataSources to our context. Later we want to bunde all those beans into the routing datasource, but then, we wont be able to know which datasource belongs to which customer. Although the customerKey is part of the bean name and the jndi path by convention, both are not rechable when injecting by the DataSource interface. Hmmm. Well we can rely on our bean name creation strategy once I think. Based on that, this is our JavaConfig for creating the AbstractRoutingDataSource:
The presented solution can generally be used for dynamically defining a set of beans of the same type which should take part in the whole Spring bean life cycle.
With reaching 1600 words, I will take a break. But I won’t leave without summarizing the open topics so far:
I hope there will be time to discuss it :)
So long! Have fun!