A common pattern for accomplishing this has been a 'search and replace' during the build process (hopefully automated using Ant or an equivalent!). There are some drawbacks to this approach:
- The artifacts you are testing in one environment are not *identical* to the (supposedly same) artifacts in another environment. Since the artifacts have been rebuilt / repackaged, this opens up a 'risk-window' during which something could go wrong. I've personally had this occur when a developer unknowingly built the production release using Java 5 (the application was 1.4 at the time), resulting in a botched production deployment even though testing was successful across all other environments.
- A 'search and replace', usually being file & text-based, can be fragile and prone to errors. Perhaps a new configuration file is added, without being added to the search pattern; perhaps there is some valid text that coincidentally matches a replacement pattern.
- On a large project with multiple environments, multiple builds can be time and space consuming, essentially multiplying your time/space needs by the number of environments.
- Replacement values are now 'hard-coded' into the deployed artifacts, and cannot be adjusted without rebuilding and redeploying (and the inherent risk therein).
Here is a sample bean definition (borrowed from PropertyPlaceholderConfigurer's JavaDoc):
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">...and the addition of a RuntimeEnvironmentPropertyPlaceholderConfigurer bean:
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
</bean>
<util:set id="runtimeEnvironments">
<value>production</value>
<value>staging</value>
<value>integration</value>
<value>development</value>
</util:set>
<bean class="digitalascent.framework.support.environment.RuntimeEnvironmentPropertyPlaceholderConfigurer">
<property name="propertyLocation" value="/WEB-INF/runtime-properties/" />
<property name="environments" ref="runtimeEnvironments"/>
<property name="defaultEnvironment" value="development" />
</bean>
Now the properties 'jdbc.driver' and 'jdbc.url' can be defined in the environment specific properties files: WEB-INF/runtime-environment/development.properties, integration.properties, staging.properties and production.properties.
By default, the implementation looks to the system property 'runtime.environment' for the environment key (e.g. -Druntime.environment=production); this property name can be changed (its a property of the bean), or for more advanced uses a custom RuntimeEnvironmentKeyResolver can be wired to to resolve the runtime environment.
The RuntimeEnvironmentPropertyPlaceholderConfigurer also, by default, enables overriding of any properties by system properties; this allows for 'tweaking' the configuration post-deployment, without having to rebuild and redeploy. In the above example, if the hostname of your database server needs to change, you can override the jdbc.url property by adding '-Djdbc.url=newUrlHere' to the relevant startup script.
The source code can be found attached to the Spring JIRA enhancement request.
Some points to note:
- Source is based on Java 5+; it should be trivial to back-port to Java 1.4 if necessary;
- The examples are based on Spring 2.0; there is nothing in the code that should prevent use on Spring 1.2.x;
- If your application has multiple Spring contexts (say a web context and a servlet context), you will need to define the RuntimeEnvironmentPropertyPlaceholderConfigurer bean in each context
- Peter Monks for providing the use-case / inspiration for the RuntimeEnvironmentKeyResolver;
- Members of the Spring community for feedback (forum post)
- Digital Ascent for sponsoring the development of this solution
5 comments:
I've implemented the exact same concept some 20 months ago (just checked svn), and it has proven to be really helpful. Too bad I missed the discussion on the forum!
The only drawback is that all settings are accessible wherever the war file is deployed. Support for encrypted property values would probably be nice (haven't come around to do this myself).
Anyway, glad to see this!
Cheers,
-Ralph.
Glad to see that others are finding this concept useful as well!
Recent modifications to Spring's PropertyPlaceholderConfigurer provide support for processing property values (e.g. decrypting them) via the convertPropertyValue method - see http://www.springframework.org/docs/api/org/springframework/beans/factory/config/PropertyPlaceholderConfigurer.html
Great idea Chris. I've posted the specification for an alternative approach here.
Chris,
This looks like a excellent solution, and I'm trying to get it set up.
However, how can I deal with multiple hibernate config files? Specify different file names (with everything copied except the few db connect properties)? Do you know of a way to override or include hibernate properties, specified in the standard config file (loaded with LocalSessionFactoryBean)?
Thanks
acme,
I've never tried this myself, but presumably you could define multiple LocalSessionFactoryBeans (with lazy="false" on each) and then reference the appropriate one as a property placeholder.
Post a Comment