How to deal with platform environment (Test, Acceptance, Production etc.) specific variables in Java applications?
Applications often need resources like databases, third-company web services, ldap servers and other external systems. It is common practice to externalize the configuration of such resources. In the case of a database dependency the use of a DataSource (hiding the complexity of configuring and connecting to the database) is a good example of this. The details of the configuration are in most cases platform environment specific. So how do we properly externalize the details of the configuration?
Basically the development team has two options:
- Build an application and specify the target environment
- Externalize the variables from the deployable unit
I have seen many development teams using the first option. They use ant, maven or maven2 to build their java application and they use a command parameter to specify the target environment of the build. It usually looks something like this:
[code]$ ant install -Denv=test[/code]
or in the case of maven2 using profiles:
[code]$ mvn -P test clean install[/code]
There are numerous ways to accomplish this using properties files, xml descriptors, filtering etc. The end result is a deployable unit that can only be deployed in the environment it was built for. I don’t like this approach. It is better to have a deployable unit that can be deployed in any environment. This is mainly because the deployment of an application should not be the responsibility of developers. It is the responsibility of the maintenance department and the environment specific variables should be part of the environment and not of the application. The final argument is that developers often have no knowledge about the acceptance or production environmental resources. For instance, how can they possibly know the username/password of the production database? So how do we make the deployable unit independent of the target environment?
First it requires that we don’t hardcode any environment specific values in the codebase. This is, apart from the problem we are dealing with, always a good practice. So we use properties files or even better (when using Spring, and you should!) we use placeholders in our application context files. An example: we are using an LDAP server to authenticate our users in combination with the excellent Acegi framework. Let us assume we have a test LDAP server and a production LDAP server.
[xml]
[/xml]
Spring already has support for resolving placeholders like ${ldap_address} during the runtime initialization of the bean factory. Simply define a PropertyPlaceholderConfigurer in your application context file that uses a properties file for pulling values into bean definitions. However the problem with that approach is that you have to wire the PropertyPlaceholderConfigurer with the location of the properties file. The developer can not make assumptions about the location of the properties file nor is it wise to do so. It would be nice if we had a generic way of wiring a PropertyPlaceholderConfigurer with the correct values. Enter Jndi! If you would have a generic hook to populate the environment of the InitialContext with the values of the placeholders then we could use the Jndi Context to lookup and resolve the specific placeholders. Most application servers have such a hook and I will show how to do this with the excellent JBoss application server.
Using the jndi.properties file in the conf directory of your server configuration you can easily pass the platform environment specific values to the InitialContext. You don’t have to hardcode an absolute or relative path into your PropertyPlaceholderConfigurer if you use the jndi.properties file. Continuing our example we would need the following key-value entry for our initialDirContextFactory bean:
[code]ldap_address=ldap://organization.intra.local.test[/code]
Add this key-value pair to the jndi.properties file located in the conf directory of your JBoss server configuration and use a customized PropertyPlaceholderConfigurer to resolve your placeholders:
[java]
public class JndiPlaceholderConfigurer extends PropertyPlaceholderConfigurer implements InitializingBean {
private Map environment;
public void afterPropertiesSet() throws Exception {
Context context = new InitialContext();
environment = context.getEnvironment();
}
protected String resolvePlaceholder(String placeholder, Properties props) {
return (String) this.environment.get(placeholder);
}
}
[/java]
With one line of xml code in one of your spring bean definitions files the post-processing of the BeanFactory initialization will take care of resolving the placeholders:
[xml]
[/xml]
Of course you need a proper procedure set up for communicating the environment specific variables your application depends on to the person/department responsible for deploying the application. Currently I’m looking at the Assembly plugin for Maven 2 to automate the process of delivering more than just the deployable application unit to the people responsible for deploying applications. In my next blog I will elaborate more on the assembly plugin and how to make sure that the application server configuration meets the demands of your application.