Automating customized development and production deployments

Making custom configuration for multiple environments
and streamlining their build
Example with Maven, Spring, and Java web application

The Problem

We frequently come in situation we need to customize config data, or even some business objects, for multiple environments where we intend to deploy our application. Usually there are at least development and production environments, but some others might be needed as well. Like for testing or multiple different productions.
And most common things that need customizing are host names, ports, file locations, of our data sources, db and other servers, paths of resources. Sometimes we use different implementations or libraries for our development and production needs. Like described inarticle about http session, we might use standard Tomcat session manager in development, and Spring Session with Redis in production.

Customizing deployments can be done in many ways. One common is to manually change config files after main application artifacts are deployed to their final locations. But this process is cumbersome. Config files are usually bundled with classes in a single file (like war), there is lot of manual work, especially if we have frequent deployments, we can easily forget to change something or put wrong data.


Solution

The best solution would be if can with one command get all customized bundles, or with just one parameter control which bundle we want to build.
This is also achievable in many ways, we will present one that is simple to implement and works well. It is for Java web application, build with Maven, and with Spring configuration. This is common setting for many applications that have this kind of problem. And all three of them have features that facilitate customization.
Spring has possibility to configure multiple environments, Maven includes different profiles, and Java web apps can have context and init parameters.


Implementation

So we will implement changes for three different aspects of web application, in three different places:
applicationContext.xml, web.xml, and pom.xml. Here is what needs to be added to all of them. Suppose our two target environments are named dev and production. And we need to configure different datasources and http session handling in them.

applicationContext.xml

		
<beans >
...												
	<beans profile="dev">
		<bean id="dataSource" class="org.postgresql.ds.PGPoolingDataSource">
			<property name="dataSourceName" value="ds1" />
			<property name="serverName" value="localhost" />
			<property name="databaseName" value="dbname1" />
			<property name="user" value="usr1" />
			<property name="password" value="pass1" />
			<property name="maxConnections" value="10" />
		</bean>

	</beans>
	<beans profile="production">
		<bean id="dataSource" class="org.postgresql.ds.PGPoolingDataSource">
			<property name="dataSourceName" value="ds2" />
			<property name="serverName" value="127.8.252.130" />
			<property name="databaseName" value="dbname2" />
			<property name="user" value="usr2" />
			<property name="password" value="pass2" />
			<property name="maxConnections" value="10" />
		</bean>
		<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration" />
		<bean class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:hostName="127.1.1.1"
			p:port="12345" p:password="rtyhertEW$%Y#$%wht" />
	</beans>
</beans >								
							
Here we added nested beans elements for our two environments, don't forget to put it at the end of file.


For web app config differences we will actually use two files. One is already existing web.xml, where dev params will reside. And the other is web-production.xml, for production.

web.xml

	<context-param>
		<param-name>spring.profiles.active</param-name>
		<param-value>dev</param-value>
	</context-param>
							
Here we made dev profile active by default.

web-production.xml
	<context-param>
		<param-name>spring.profiles.active</param-name>
		<param-value>production</param-value>
	</context-param>
	...
	<filter>
		<filter-name>springSessionRepositoryFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
	</filter>
	<filter-mapping>
		<filter-name>springSessionRepositoryFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
							
Here we made production profile active, and set filters to activate spring session.


pom.xml

	<profile>
		<id>production</id>
		<build>
			<plugins>
				<plugin>
					<artifactId>maven-antrun-plugin</artifactId>
					<version>1.7</version>
					<executions>
						<execution>
							<id>delwebxml</id>
							<phase>prepare-package</phase>
							<configuration>
								<target>
									<delete file="target/${project.build.finalName}/WEB-INF/web.xml" />
								</target>
							</configuration>
							<goals>
								<goal>run</goal>
							</goals>
						</execution>
					</executions>
				</plugin>
				<plugin>
					<groupId>org.apache.maven.plugins</groupId>
					<artifactId>maven-war-plugin</artifactId>
					<configuration>
						<webXml>src/main/webapp/WEB-INF/web-production.xml</webXml>
					</configuration>
				</plugin>
			</plugins>
		</build>
	</profile>								
							
Here we made Maven profile for creating production artefact. So if we just type mvn package development war will be created. And if we type mvn package -Pproduction then instead of web.xml, web-production.xml will be put in its place, Spring will be configured with production profile, and we have ready war for deployment. That's it!

Comments