How to Load Config Files with the Strategy Pattern

How to Load Config Files with the Strategy Pattern

Some developers have a bad habit of deploying their configuration files in the same package as their code.  Not the Spring Framework type of config that’s really code in disguise, but the server specific kind.  The kind that’s different for each environment.  These teams create separate builds for each environment, they rebuild packages for every config change, and they’re never 100% sure the tested code is what made it into production.  Trust me, you never want to be the guy comparing war files when s#*! hits the fan in production.

 

 

Loading Strategies

The better approach is to always keep the changing bits out of your war and ear files.  The problem then becomes how to load these bits in a way that isn’t too tightly coupled to the code.

Enter the Strategy design pattern.  This pattern let’s you select between several algorithms at runtime. In this case, it’s used to choose the best implementation to load your config files.  The different implementations will look for the named config file in a series of locations which you specify using either:

  1. A JVM argument
  2. An environment variable
  3. The servlet context (web.xml)
  4. The container’s config folder (Tomcat’s conf folder)
  5. The classpath

This way your app can load properties from the container’s config folder in production, but override that behaviour with a JVM arg while you’re developing in an IDE.

Service API

The actual config loading strategy selection is best wrapped inside a simple ConfigService facade.

The facade has a single method that takes a path and either returns a Properties object or throws a RuntimeException if no file is found.

Implementation Details

Config Loading Classes

Each strategy is created as a separate class that implements the ConfigLoader interface.

The interface’s contract is pretty simple: return a java.util.Properties if the specified file is found, or null if no file exists.  This way you can pass the file path to each strategy in turn and stop at the first one that returns a file.

Strategy 1: Load from a JVM Argument

Since this strategy is tested first, you can preempt all other strategies with it.  Just specify the folder containing your files.

The JVM arg strategy, like most others, has a specific folder it tries to load from.  The FileConfigLoader class factors out the loading logic for these single-folder implementations into a protected method: getConfigImpl(String folder, String filename).

With the loading code removed, this strategy is basically one call to System.getProperty.

 

Strategy 2: Load from an Environment Variable

The environment strategy is also a single call to System.getenv.

 

Strategy 3: Load from the Servlet Context

The servlet context strategy is a bit more tricky since we have to keep track of the current ServletContext to load from.  This is accomplished by making the ServletContextConfigLoader implement ServletContextListener and registering it in the web.xml.

In the web.xml deployment descriptor, you register the ServletContextListener and set the folder it searches for config files.

Strategy 4: Load from the Container’s Config Folder (Tomcat Conf)

You can certainly put application config files into your container standard folder — although not everyone will agree with this.

If you’re using Tomcat it’s the conf sub-folder.  You can get the root folder by reading the catalina.base JVM arg.

Strategy 5: Load from the Classpath

The final strategy reads from the classpath.  This includes any jar or folder in the web app or container’s classpath (that’s available to the app).  The logic here uses the current thread’s classloader to read the file if it exists.

Putting it all Together

The last step is for the ConfigService to try each strategy in succession.  The first implementation that returns a non-null Properties object wins.

 

Conclusion

One sign of a mature software delivery process is the ability to create a single build and deploy it to all environments.  This guarantees the production package is the exact same one — timestamped, md5, etc. to prove it — that your testers signed off on.  Keeping your config files out of your war and ear files will not only make setting easier to change, but it’ll also save you time troubleshooting and increase the quality of your releases.

 

 

About Dele Taylor

Dele Taylor is the founder of StackHunter.com -- a tool to track Java exceptions. You can follow him on Twitter, G+, and LinkedIn.

Trackbacks/Pingbacks

  1. Optimizing MySQL Queries with Spring's JdbcTemplate - Stack Hunter - February 12, 2014

    […] that took more than 10 milliseconds.  This conditional execution is hard-coded, but you can make it configurable in your own […]