Can I use a single war file in multiple environments? Should I?
I have a Java web application at my work and I'd like simplify how we deploy to our DEV, QA, and PROD environments.
The application reads in a series of properties at startup, and the properties files are different for dev, qa, and prod. Whenever I want to deploy to a certain environment I drop the environment-specific properties file into my app folder, build the war, and then deploy it to one of the three tomcat 5.5 servers.
What I would like to do is have to have a single .war that has the properties for all environments, and have the app interrogate the webserver during the init process to figure out which environment the app is in, and hence which properties to load. Is there an easy way (or, failing that, a standard way) to do that?
This really depends on what you are using those properties for.
Some (like data source, for example) can be configured in the container itself (Tomcat 5.5. JNDI Resources, see JDBC sources section as well).
Others (application-specific) may indeed need to be properties. In which case your choices are:
- Bundle properties within WAR file and load the appropriate subset based on some external switch (either environment variable or JVM property)
- Setup a deployment process on each of your servers where war is unpacked and a property file (located in a predefined location on that server and specific to that server) is copied over to WEB-INF/classes (or other appropriate place).
As far as "is this a desirable goal" goes - yes, I think so. Having a single WAR to test in QA / staging and then deploy to production cuts out an intermediate step and thus leaves less chances for mistakes.
Update (based on comment):
Item #1 above refers to an actual environment variable (e.g. something that you set via SET ENV_NAME=QA in Windows or ENV_NAME=QA; export ENV_NAME in Linux). You can the read its value from your code using System.getenv() and load the appropriate properties file:
String targetEnvironment = System.getenv("TARGET_ENV"); String resourceFileName = "/WEB-INF/configuration-" + targetEnvironment + ".properties"; InputStream is = getServletContext().getResourceAsStream(resourceFileName); Properties configuration = new Properties(); configuration.load(is);
But yes, you can instead define a scalar value via JNDI (see Environment Entries in Tomcat doc) instead:
<Context ...> <Environment name="TARGET_ENV" value="DEV" type="java.lang.String" override="false"/> </Context>
and read it within your app via
Context context = (Context) InitialContext().lookup("java:comp/env"); String targetEnvironment = (String) context.lookup("TARGET_ENV"); // the rest is the same as above
The thing is, if you will be using JNDI anyway, you might as well forgo your property files and configure everything via JNDI. Your data sources will be available to you as actual resources and basic properties will remain scalars (though they will be type safe).
Ultimately it's up to you to decide which way is better for your specific needs; both have pros and cons.
What you do is an accident waiting to happen... One day a DEV war will end up in de PROD server, and by some law superior to all laws of nature that problem will be detected at 2AM. Can't explain why this is the case, but some day that will happen. So one war is in my opinion definitely a good idea.
You can set a system property in the respective JVM's (-Dcom.yourdomain.configpath=/where/you/store/configfiles) and fetch this property with
String value = System.getProperty("com.yourdomain.configpath", "defaultvalue_if_any");
The default value could point somewhere inside the war (WEB-INF/...), or if there's no default, be used to make some logging noise during load to warn for misconfiguration). Also note that this technique is not platform dependent, so you dev machine can be a Windows box and the server a Linux machine, it can cope with both. We normally create a subdir per application in this configpath, as several applications use this system on a server, and we want to keep things tidy.
As an added bonus, you don't risk to trash manually tweaked property files on a PROD server this way. Just don't forget to include the path where the files are stored in a backup scenario.
I think a single war file is a good way to go, because its nice to have confidence that the binary you tested in DEV is exactly the same as in Production. The way we do it, we keep the configurations in a separate properties file, outside the war, but in the app server's class path.
If you want to keep all the properties inside the war (which does make deployment easier, because then you don't have to also deploy a properties file), you could keep a single properties file in the classpath that identifies the server environment type, and use that to key values in the properties file within your .war file. The external properties file may also be a good way to go for maybe some high-level configurations that don't change much and are used across a number of war files.
Probably the simplest way would be to set up an environment variable that differs between the application services and use this to determine which property file to read.
Another possibility would be to store the properties in a database, and use a datasource that exists under a standard JNDI name, but points to a different place in the various environments.
I prefer the one EAR or one WAR approach. There is something re-assuring and often required from a security standpoint about taking the exact same EAR that was just tested and moving it directly into the next state (test > Production).
There are also many options besides properties files provided by the container. Often the container has a nice UI to maintain those values and resources when you are dead and gone.
There are countless examples of using a database backed ResourceBundle.
For example, the Spring framework has several mechanisms to make that happen without much effort. Starting with PropertyPlaceholderConfigurer
Set a Systemproperty at startup that points to the location of your properties file, and then in your application pull in this property and load your settings. Another thing I do is have two properties file, something like default.properties, and external.properties. They contain the same properties, but the default.properties contains the default(works most of the time settings), this file goes in the war. Then if you deploy to an env. you look for the external.properties, if found that is used, if not then you rollback to the default.properties. This provides a nice way to override properties if needed, but also have a default setup. This works in a lot of my deployments, but may not in your scenario.
Absolutely a single WAR is the best way to go. Set the resources using the same JNDI names in each environment, and if you need to detect which environment you're in for business logic purposes, use a System property on Tomcat startup.
A single build (war) is certainly the right approach. However, when it comes to environment specific configuration, the best way to go is to ensure that all configuration .properties files should not be pushed to all the environments. e.g. PROD properties files should be copied to DEV or UAT. Spring profiles also should be avoided as they lead to convoluted configuration management.