tags:

views:

631

answers:

8

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?

A: 

I don't think a one-size-fits-all WAR is your best solution. For starters, what would you use to distinguish between the systems in your application? What if your DEV, QA, and PROD systems had the same OS and hardware? I recommend that you use a build tool, such as Maven or Ant, to build a WAR for each system. Build tools such as these allow you to create multiple build profiles for different environments. You can configure the build tool to package the correct properties file into the WAR depending on the build profile(maven) or target(ant). You can even create a build profile to generate all three WARs; each containing the correct properties file.

hoffmandirt
This adds complexity in the build and deployment process that can be avoided using JNDI resources, and if necessary, environment-specific properties files and a system property to tell the server's JVM which environment it is running.
Kaleb Brasee
I have never seen JNDI used before in the few companies that I have worked for. We have a pretty good CM plan in place and using multiple build profiles works great. The default build is for development machines. The continuous integration server uses another build profile and when we cut a release for the customer, we use the release profile. The customer doesn't have to set any flags. Maybe it's the CM plan that makes it all happen. Either way, it's better than deploying the WAR and manually swapping property files in and out.
hoffmandirt
A bad idea on multiple levels. Straight away the number of artifacts that need to be managed is multiplied by the number of environments you have.
Pablojim
+3  A: 

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:

  1. Bundle properties within WAR file and load the appropriate subset based on some external switch (either environment variable or JVM property)
  2. 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.

ChssPly76
Some of the properties are DB properties, but we also have stuff like ldap servers and smtp server addresses that vary based on environment. I confess I'm a JNDI noob -- can I store that sort of information in there?I suppose ultimately all I need is one variable (e.g. "environmentName") with a value of dev|qa|prod|whatever. I could use that as a prefix to index into the environment-specific portion of a single properties file. Is JNDI an option for that?
NobodyMan
I've updated my answer above; comment space was too constricting.
ChssPly76
Excellent info- many thanks! I think I'll opt for the JNDI approach.
NobodyMan
+1  A: 

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.

fvu
Good info - thanks. Quite funny too, or at least it would be if it didn't hit so close to home. The ops team accidentally loaded our QA war into PROD awhile back. This event that was the precursor to my question here ;-)
NobodyMan
A: 

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.

Chris
+3  A: 

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.

Spike Williams
A: 

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.

Kaleb Brasee
+1  A: 

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

Brian
A: 

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.

broschb