Wednesday, 21 August 2013

Configuration done right

In my previous post about the topic, I've talked to you about my problems with the typical configuration methods. I've compiled a list of values that I'd expect from a configuration system and set my mind on making it happen. These points were:
  • should be dead simple to use
  • should support extension
  • should support scopes
  • should support namespaces
  • should support optional configuration files for developer local configs
  • should allow stating that a new configuration file should override the keys or not
  • should not need an object representation in the code
Over the past weeks I've created a solution that we've tested on several projects already here at The Guardian. I'll take you through the current state.

Should be dead simple to use

For me, the statement above conveys two things. One, it is really easy to add the tool as a dependency. Two, I only need to tell what configuration sources I intend to use.
Many open source projects tend to have their own maven distribution ways, and require you to add a custom repository to your pom. While that might work with small companies and private projects, regulated organisations seem to have difficulties with untrusted sources. Not to mention that adding a 3rd party repository config might consist of adding twice as many lines to your pom, which in my opinion is "hard to use". For the sake of the easy dependency handling, I've released it through sonatype's OSS maven repository which allows anyone to load it without any extra config. 
How to release your own project through sonatype

Importing the library


        <dependency>
            <groupId>hu.meza.tools</groupId>
            <artifactId>config</artifactId>
            <version>LATEST</version>
        </dependency>

Defining a configuration source stack

The second point is ease of usage with regards to sane coding strategies. It seems really easy to just create a globally accessible static class that every living being in our code knows of and can use. How many times have we imported Config from out of the blue and wrote the following?
Config.getString("someSetting");
It is easy to use until you start testing your code and realise that you need to be thinking of mocking statics and global state.
This boils down to the difference between a Singleton and a singleton. But let's not jump ahead of ourselves. I'd like to take the chance and show you how to define a configuration source stack, which is the next step after importing the library.
Config config = new Config();
File frontendConfigFile = new File(path);
config.add(new Optional(new FileConfiguration(frontendConfigFile)));
config.addOverriding(new Required(new ResourceConfiguration("environment.properties")));
config.addOverriding(new Optional(new ResourceConfiguration("developer.properties")));
config.addHighOrder(new SystemPropertiesConfiguration());
What this does in what is says it does:
  1. Add an optional file source
  2. Add a required resource source and override any keys that match from the previous sources
  3. Add an optional resource source and override any keys that match from the previous sources
  4. Add a higher order source from the system properties that will stand above all previous ones if the keys match
Now you might notice that Required and Optional are not always used. If omitted, it'll behave however the source is configured to behave. With this, we've covered another constraint:
  • should support optional configuration files for developer local configs

Should be extendible

Being able to alter behaviour if needed is one of the most important things for me. As a library producer, I wouldn't want to limit anyone in how they use my solution. I think of it as a foundation others could build upon.

So how do I use this?

Currently I am working in Cucumber-JVM land, where I use picocontainer as my dependency injection container. Unfortunately cucumber-picocontainer is not configurable at this moment, so in order to create a properly set configuration for my apps, I need to wrap this solution into a standalone object, and pass that along to anyone who might need it.
public class Configuration {

  private final Config config;

  public Configuration(Config config) {
    this.config = config;

    final SystemPropertiesConfiguration systemPropertiesConfiguration =
      new SystemPropertiesConfiguration();

    Config system = new Config();
    system.add(systemPropertiesConfiguration);

    String userHome = system.get("user.home");
    String path = String.format("%s%s.gu%sfrontend.properties", userHome, File.separator,
                  File.separator);
    system = null;

    File frontendConfigFile = new File(path);
    config.add(new Optional(new FileConfiguration(frontendConfigFile)));
    config.addOverriding(new Required(new ResourceConfiguration("environment.properties")));
    config.addOverriding(new Optional(new ResourceConfiguration("developer.properties")));
    config.addHighOrder(systemPropertiesConfiguration);
  }

  public String baseUrl() {
    return config.get("baseUrl");
  }

  public String cookieString() {
    return config.get("userCookie");
  }
}

I ended up liking this solution, as the mapping between functional meaning and configuration keys are done in a single point. My application specific configuration stack knows about how to get information out of the sources, while my code only knows what to ask for. This leaves me without pain when I need to change a configuration key.

I assume that when you can access you DI container's configuration, you can easily create a provider for the Config object that can do all this setup.

Where to use?

It can be tempting to pass the whole Configuration object to everyone who might need a single bit of information out of it. I tend to believe that an object - or method for a matter of fact - should only know of things that it directly uses. For example, I wouldn't pass the whole Configuration to a class which only reads out a single setting. I would probably pass the Configuration to the factory which produces the object in question, and add that particular setting as a parameter to the construction (or method call).
The general rule is the less classes it collaborates with, the more flexible my code is. At least that's how I think about it. 

Should support scopes

Scopes come in handy with applications that for example use plugins. Your plugin might want to know about how the main application is configured, but not vice versa. The application, or other plugins should not even be aware of your plugin.
To achieve this, the only thing you need to do is to pass the parent Config in when you create your local one.
Config config = new Config(hostApplicationConfig);
Easy?
From this point in time, your local config instance will see everything that the hostApplicationConfig holds, and everything that you add in later. The latter however, will have no clue that it's decorated.
/**
 * a.properties:
 * setting_a=aaa
 */

Config hostApplicationConfig = new Config();
hostApplicationConfig.addOverriding(new Required(new ResourceConfiguration("a.properties")));

/**
 * b.properties:
 * setting_b=bbb
 */

Config config = new Config(hostApplicationConfig);
config.addOverriding(new Required(new ResourceConfiguration("b.properties")));
The above example deals with two configuration files. One is loaded to the hostApplicationConfig which is then passed to the config, which then loads another configuration file.
Because of the scoping, I could do:
config.get("setting_a");
config.get("setting_b");
but cannot do:
hostApplicationConfig.get("setting_a");
hostApplicationConfig.get("setting_b");
because the hostApplicationConfig only knows of setting_a.

Should support namesapces

This is a feature that I am currently not using, but I've suffered from the lack of it before.
Being able to define namespaces is important when you are not able to change configuration keys and you have conflicting keys. This can happen if multiple applications are using the same configuration source and things get messy. If you need to go there, here's what you can do:
/**
 * a.properties
 * setting_a=AAA
 */

/**
 * b.properties
 * setting_a=BBB
 */

Config config = new Config();
config.addOverriding(new Required(new ResourceConfiguration("a.properties")));
config.addOverriding(new Required(new ResourceConfiguration("b.properties")), "newApp");

config.get("setting_a");
config.get("newApp.setting_a");

You load your properties into a "newApp" namespace. The only effect it has is that you need to ask for the setting in that particular namespace by adding "newApp." in front of the setting key.

Should allow stating that a new configuration file should override the keys or not

I've briefly touched Required and Optional before. These control the way the library behaves when the configuration source could not be loaded for some reason. If it's required to be present and you state that with Required, it'll throw a CouldNotLoadConfigurationSource exception.

Should not need an object representation in the code

As you could see from the previous examples, we're not mapping configuration to any internal model.

Summary

I've spent a great amount of time fiddling with different ways of doing this, and even more time testing out the version I've described above. It seems easy to install, comfortable to use, readable and extendable.The best way to operate this configuration solution is to use it through a dependency injection container, pass it to a factory and feed in the requested data as parameters to other objects.
Please feel free to contribute with other loaders and ideas.
Fork the project at: http://github.com/meza/config
I'd be happy to see implementations in other languages too.

No comments :

Post a comment

Note: only a member of this blog may post a comment.