What is TreeConf?
TreeConf is a configuration framework library, that offers a new kind of configuration paradigm for Java applications.
The main characteristic of this framework is that it structures the configuration in a static containment tree, and the actual configuration is based on reflection (in the Java meaning of it).
The configuration items are not kept centrally in a single repository, but rather configuration items can be nested into each-other, and the access to any configuration value is done by traversing the containment tree.
In addition, new features are brought to the user: configuration change notifications, and the possibility of IDEs to help navigate in the configuration tree for the desired item.
But before delving into the details of TreeConf, let's have a look at how current configuration frameworks are used.
General aspects of configuration
Usually when using anything (including a software library), we prefer to have an easy ride.
That means that we prefer that the tasks we perform often are easy, at the expense of tasks that need to be performed seldomly, that can be more or less complicated.
For example, when owning a car, we prefer to have the driving part easy and the maintenance more difficult.
Nobody would buy a car that would require to hire a pilot to drive around daily, if on the other hand we could maintain and fix it by ourselves (there are of course some lucky people that can afford to pay others to drive them around).
It is rather preferable that we can drive the cars ourselves, and we afford to pay a professional for maintenance and repairs (which hopefully doesn't need to be done very often).
That being said, what are the tasks involved when using a configuration framework?
- Configuration item creation. This taks is done once.
-
Configuration item access (for reading and writing). This is done in many places throughout the code:
- When setting first time the values (may coincide with the item creation)
- When reading the values stored
- When changing the values
Therefore, a good configuration framework makes it easy to access the configuration items, at the expense of creating them.
Current (flat) configuration frameworks
In the classic "flat" approach, the existing configuration frameworks allow users to add configuration items by using a <key,value> pair approach.
This means that all the configuration items (or rather the keys) could be accessed from a single "central configuration key repository".
In everyday life, such repositories are simple container classes that have all the key names defined in a static way. For example:
public class ConfigurationKeyNames {
...
public static final String LOCAL_PORT_NUMBER = "lpn";
public static final String REMOTE_PORT_NUMBER = "rpn";
public static final String REMOTE_HOST_NAME = "rhn";
...
}
The values chosen in this example for the public fields are a bit cryptic.
This was intentional, to exemplify an existing issue which will be discussed later.
In these cases, the user (software developer) will have to properly group the various key names in the code (based on the configured area, or software architecture, etc) so that it would be easier to maintain.
Whenever the user wants to store/change or read a configuration item, (s)he would write something like:
set(ConfigurationKeyNames.LOCAL_PORT_NUMBER, "10000");
...
portNumber = get(ConfigurationKeyNames.LOCAL_PORT_NUMBER);
This allows very easy creation of new configuration items (<key,value> pairs).
However, there are several challenges with this approach:
- There is the risk that several parts (or modules) of the same software could unintentionally create several different keys for the same configuration item (especially if different people are responsible of different parts in a software)
-
Every time a configuration item should be read, the name has to be chosen from a large set of keys (the central key repository), which is flat and non-intuitive; basically slowing down the developer
The first issue can be avoided by carefully naming the key fields in a descriptive manner.
Because many times there are configuration items controlling similar parameters belonging to different module in the code, there is a tendency to describe the parameter key name field in a top-down manner.
In other words, the nested module names are concatenated, followed by the actual parameter name.
For example:
- SERVER_DATABASE_CONNECTION_PORT
- SERVER_DATABASE_CONNECTION_HOST_NAME
- SERVER_DATABASE_CONNECTION_TIMEOUT
- SERVER_DATABASE_AUTHENTICATION_USER_NAME
- SERVER_DATABASE_AUTHENTICATION_USER_PASSWORD
- SERVER_DATABASE_AUTHENTICATION_ALGORITHM
- and so on...
For the second issue, there is basically no workaround.
Even if the user is using advanced IDEs that help him to quickly choose the next possible action when writing code, (s)he still needs to scroll down a long list of key names and search for the key name (s)he wants.
Moreover, even if the developer knows exactly what configuration item (s)he wants to access, typing the first letters of the name of the key doesn't help the IDE to suggest a single name for the key name, since there are many key names having the same prefix (see the list above).
So as we can see, in the classic approach it is:
-
Relatively easy to create new configuration items
- users just edit the key containment class and add the new keys, but in order to keep the code maintainable, they need to group the key definitions in the source code
-
Harder to access existing configuration items
- users have to pick the key names from a long list
- IDEs cannot help too much the developers by suggesting a single key name
-
The user code looks rather cluttered, and harder to read
- accessing configuration items involves calling many methods and using long key names
TreeConf configuration framework
TreeConf takes a different approach to configuration.
Before writing this library, the author (like any developer) had to use the existing, conventional configuration libraries, like Java's own Properties class, and others like it.
The code was always cluttered and slow to write.
Letting the user write fast his/her code when using your library, can be as desirable as having a full-featured solution for them.
As learned in the world of programming languages, sometimes it pays out more to be able to develop fast your application, than to be able to do crazy things with the code.
As mentioned before and described in its name, TreeConf keeps the configuration in a tree structure rather than in a flat structure (see the ConfigurationKeyNames class above).
Because the items are organized in a tree structure, it means that the configuration has one root, any number of nodes and any number of leafs.
In the TreeConf context:
- the root is a node with capabilities to save and read from an arbitrary IO system
- the nodes can contain any number if other nodes or leafs, but do not contain actual data
- the leafs contain actual data, but cannot contain any other nodes or leafs
The major (and most important) difference that TreeConf brings, is the fact that configuration nodes are backed by classes.
For each different configuration node, a class is created. The class is a container class (it doesn't have any methods).
The fields in those classes are the actual sub-nodes or leafs in the configuration tree.
So it seems it is rather a bit more difficult to create the configuration items.
On the other hand it is really easy to use them afterwards.
The keys being nested are not all in the same central location from where it is difficult to select.
The IDEs can help the developer to make the next selection fast.
Since the name of the configuration item is given by the actual variables in the code, the user is prompted at compile time if there is a typo in the name of the configuration item it is using.
Also, this leads to the fact that configuration items can be renamed automatically throughout the program by simply letting the IDE to do the job for you.
When using the TreeConf library, accessing the configuration looks like this:
...
short portNumber = database.server.connection.port.toShort();
String hostName = database.server.connection.hostname.toString();
database.server.authentication.user.name.setValue("user");
...