Configuration, Part 2

In my previous post I talked about general configuration best practices. In this post, I’ll detail the specifics of how I’ve configured my latest project.

First, here are some relevant details about the project:

Processes are always kicked off from a shell script that lives in the $APP_HOME/bin directory. All of my scripts in the $APP_HOME/bin directory start with the following lines:

#!/bin/bash
source $(dirname $0)/../.include

Line 1 tells the shell to use /bin/bash to execute the script. Line 2 sources .include. Here’s .include:

#!/bin/bash

cd $(dirname $0)/..
BASEDIR=$PWD
cd - >/dev/null

source $BASEDIR/.env
source $BASEDIR/.functions

version=$(cat $BASEDIR/.version)

if test -r $BASEDIR/.source-version-hash; then
  source_version_hash=$(cat $BASEDIR/.source-version-hash)
else
  source_version_hash=NONE
fi

if test -e $BASEDIR/etc/override.properties; then
  if test -n "$APP_HOME" -a "$APP_HOME" != "$BASEDIR"; then
    fatal "Your APP_HOME is misconfigured"
  fi
fi

if test -n "$APP_HOME" -a "$APP_HOME" != "$BASEDIR"; then
  if test -e $BASEDIR/etc/override.properties; then
    fatal "Your APP_HOME is misconfigured"
  fi
fi

if test -z "$APP_HOME"; then
  if test -e $BASEDIR/etc/override.properties; then
    info "Setting APP_HOME to $BASEDIR"
    export APP_HOME=$BASEDIR
  elif test -e $HOME/opt/$APP_NAME/etc/override.properties; then
    info "Setting APP_HOME to $HOME/opt/$APP_NAME"
    export APP_HOME=$HOME/opt/$APP_NAME
  else
    fatal "Please set APP_HOME"
  fi
fi

if test -r $APP_HOME/etc/env; then
  source $APP_HOME/etc/env
fi

This file’s main purpose is to setup configuration. It checks that required variables are set (or that there is a sensible default) and it loads additional configuration.

Line 7 loads configuration from a .env file. This is default configuration (suitable for a development environment) for shell scripts. Configuration loaded in this step can be overridden by line 43 (e.g. to load production configuration values).

Lines 18-40 attempt to set and/or check the APP_HOME environment variable. Configuration override files are stored in the $APP_HOME/etc directory. This script has to deal with both development and deployed (e.g. qa or production) environments.

In development, the expectation is that there is an APP_HOME environment variable set to a directory that is not part of the codebase. This way, a developer’s custom configuration will not be checked in. The default location for APP_HOME is $HOME/opt/$APP_NAME. With this, a developer can pull down the codebase and mkdir -p $HOME/opt/$APP_NAME/etc to get started. (I actually have a /dev-bin/bootstrap script that takes care of this.)

In a deployed environment, $APP_HOME and $BASEDIR must be equal. I typically deploy to /opt/$APP_NAME, so this means that there must be an /opt/$APP_NAME/etc directory.

After .include is sourced APP_HOME is set and we have verified that we have a configuration directory. We’ve also loaded environment specific shell variable overrides (line 43 in the file above).

Now we can start up our app. I won’t show my complete startup script here (that can be the topic of another post) because the part that relates to configuration is actually pretty short.

java -classpath $CP -Dapp.home=$APP_HOME co.anthology.common.spring.Main "$@"

You can ignore the $CP variable. The main thing to notice here is that $APP_HOME is passed to the JVM as a system property using a -D option.

The final part of configuring is handled by Spring. Here’s a snippet from a Spring context file:

<bean
    class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
    p:system-properties-mode-name="SYSTEM_PROPERTIES_MODE_OVERRIDE"
    p:ignore-unresolvable-placeholders="true"
    p:ignore-resource-not-found="true"
    >
  <property name="locations">
    <list>
      <value>classpath:common.properties</value>
      <value>file://${app.home}/etc/override.properties</value>
      <value>file://${app.home}/etc/local-override.properties</value>
    </list>
  </property>
</bean>

Configuration values are applied in these Spring context files like this

<bean
    id="my-id"
    class="my.class.Name"
    p:property-1="${common.property-1}"
    />

In the above example, property-1 will get the configured value of common.property-1. (Have a look at the Spring docs for more about this.)

The value of common.property-1 is set in the common.properties file (line 9 above). It can be overridden by override.properties (line 10), local-override.properties (line 11), and finally by a system property (line 3).

common.properties is a source controlled default configuration file (all defaults should be dev friendly). It is deployed in a jar file.

override.properties is a deployed configuration file that is also under source control. There is one of these for each deployed environment (e.g. qa and prod). I typically store these files under /config/{qa,prod} in my codebase.

local-override.properties is an optional file (technically they’re all optional because of the ignore-resource-not-found="true") that lives on the deployment hosts. I typically don’t need this, but if I ever needed a way to configure a host differently than the others, this is how I would do it.

Finally, I can set a system property at startup time e.g. -Dcommon.property-1=xyz to override all previous configuration values.

Okay, that’s a short overview of how I’m configuring my latest project. Configuration may not be the most sexy topic, but it’s definitely an important piece of any project.