One WAR for development, staging/QA, and production

One of our objectives at work with our build process is that whichever artifact (jar/war) gets built, that it can be exactly the same everywhere, whether it's running on your workstation, cruise-control, qa staging server, production, etc. In other words, when we go to deploy the production WAR we don't want to have to build a custom “production” version just because the log4j properties or database connection information is different between deployment environments. My feeling is that this is a very good thing because when the WAR passes QA, we can just take the WAR from the QA server and move it into production.

There are many different ways to achieve this goal. Karl Baum blogged about his approach a while back. I thought I'd share how Andy set this up at work using Spring. So far it's been working great for us.

Essentially we allow properties in the WAR or Jar to be overridden using java run-time parameters as follows: java -Dhibernate.dbhost=mysql-prod.domain.com -Dhibernate.connection.username=produser -Dhibernate.connection.password=prodpass etc…. So in each environment we we have JAVA_OPTS set so that when we run Tomcat or run our batch jobs that reside in a Javaapp Jar, we don't have to build a new artifact for each environment.

Spring comes to the rescue here with it's propertyPlaceholderConfigurer bean. Here's what it looks like:

    <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>classpath:conf/deployment.properties</value>
                <value>classpath:conf/database.properties</value>
            </list>
        </property>
        <property name="systemPropertiesModeName">
            <!-- allow system properties to override ours -->
            <value>SYSTEM_PROPERTIES_MODE_OVERRIDE</value>
        </property>
    </bean>
    
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource"
        lazy-init="true">
        <property name="driverClassName">
            <value>com.mysql.jdbc.Driver</value>
        </property>
        <property name="url">
            <value>jdbc:mysql://${hibernate.dbhost}:3306/somedb?autoReconnect=true</value>
        </property>
        <property name="username">
            <value>${hibernate.connection.username}</value>
        </property>
        <property name="password">
            <value>${hibernate.connection.password}</value>
        </property>
    </bean>

So by default the WAR or Javaapp Jar will get it's properties such as database connection information from database.properties but anything in database.properties or deployment.properties can be overridden.

The major downside with this approach is that it won't work well in a hosted environment with lots of WAR files deployed on the same app server. If you've tackled this problem in a different way that works let me know how you've gone about it!

This entry was posted in Java. Bookmark the permalink.