Skip to main content

Configuration Blues

October 10, 2003

{cs.r.title}






Have you ever noticed how some applications seem to configure themselves? I
don't mean that they auto-detect their settings; rather, the configuration
process and tools are so well designed that they are a pleasure to use.
Like most things in development, this level of functionality didn't appear
by accident. "Application configuration deserves careful design -- perhaps
even more than application code." (Halloway, 02) If we want to offer a
similar experience to all our users, we need to stop treating configuration
as an afterthought.

There are a number of options available to Java developers when it comes to
implementing configurations. This article begins with the classic
Properties class and continues on to the new Preferences model. It ends with
an overview of Java Management Extensions (JMX). Along the way, I discuss
the various strengths and weaknesses of each option, and attempt to place
them in the broader context of a "configuration language." In other words,
if there was an ideal configuration model, what attributes and processes
would the model have, and how well does each of the existing options address
this ideal?

Although this article does not directly discuss auto-detection,
I strongly recommend this valuable technique when it's possible. There
is no benefit in requiring users to supply values that can be
programmatically determined. It's a nuisance to the user, and presents an
opportunity for error. On the other hand, there are limits to
auto-detection, especially in a platform-independent environment like Java.
Without belaboring the point, no auto-detected value should ever be treated
as more than a default. There needs to be an override available. The
easiest way to enable this is to provide a configurable property.
Auto-detection doesn't do away with properties, it underscores the need for
them.

Property Lines

Typically, we approach the design of our application's configuration in an
ad hoc manner. In other words, we add properties as they occur to us during
development. Perhaps the primary reason for this is because we can. Most
Java applications handle configuration using the Properties class. This
class allows us to add properties at will. For instance, the following code
shows how we can easily create, set, and save a couple of properties with a
minimum of fuss.

try {
    // Create and set the properties
    Properties properties = new Properties();
    properties.setProperty("propertyOne", "valueOne");
    properties.setProperty("propertyTwo", "valueTwo");
    // Save the properties
    properties.store(new FileOutputStream("app.properties"),"header");
}
catch (Exception e) {
    // Handle exception as needed
}

Retrieving the values of the properties is no harder.

try {
    // Load the properties
    Properties props = new Properties();
    properties.load(new FileInputStream("app.properties"));
    // Retrieve individual properties
    String prop1 = properties.getProperty("propertyOne","defaultOne");
    String prop2 = properties.getProperty("propertyTwo,"defaultTwo");
}
catch (Exception e) {
    // Handle exception as needed
}

Given the ease with which the Properties class allows us to create simple
configurations for our applications, there is little incentive to do more.
Unless you are building an application that will be widely deployed, there
doesn't seem to be much concern given to the task. In addition, developers
are usually judged by other criteria. The unconscious use of the Properties
class seems almost inevitable.

For all its inevitability, though, this behavior does have its problems.
Since configurations are so easy to implement using the Properties class,
and there is no immediate penalty associated with its use, cleaning up an
application's configuration can continue to be treated as an end-game
activity; the mess will be picked up later. Unfortunately, this laissez faire practice can lead to the duplication of properties and coding effort. Furthermore, individual properties and their syntax often go
undocumented in the heat of development. This can easily cause later
conflict and confusion regarding them. Finally, there is usually little or
no validation applied to most of these ad hoc properties. In my own
experience, I can't count the number of times I've been brought in to
troubleshoot an application where the problem was due to an incorrect or
missing property value that was not properly checked.

A secondary concern with all configurations, especially those built using
the Properties class, is the ease in which we create global data. One of
the tenets of object-oriented programming is the encapsulation of data.
There are no global variables in OO. Yet, encapsulating all the data is
difficult, and many of us, myself included, will resort to tricks. One such
trick is to create singleton classes that are actually little more than
clusters of global variables. Much the same may be said about our use of
the Properties class. Many applications are riddled with small chunks of
code like the samples shown above. We get properties. We set properties.
We don't normally think of this as a way of skirting around the data-encapsulation rule, but its net effect is just that. We silently introduce
global variables, and then don't see them as such.

The costs associated with these problems vary. Certainly, direct support is
the most obvious. Subtler costs may arise with awkward deployments and lack
of central management. Poorly implemented configurations may be difficult
or impossible to administer remotely. End users may be prevented from
self-servicing their applications. At its worst, code changes may be
required for something that could easily have been accomplished by a
property, had configuration been duly considered. The more ad hoc the
process, the less likely it is to minimize these costs.

Preference for Preferences

In some regards, the Preferences class is a vast improvement over
Properties. Like its predecessor, Preferences is lightweight and very
straightforward to use. It differs from Properties in at least two ways.
First, it introduces some notion of scope. It distinguishes between system
and user preferences, and ties each preference to an individual class.
Second, Preferences allows you to remain ignorant of where and how your
configuration is persisted. The Properties class requires you to supply an
OutputStream to its store method. You can't help but be aware of where and
how you're persisting the configuration. In contrast, the Preferences class
doesn't have an explicit store method, much less a way to direct its
persistence. A pluggable adapter handles the messy details for the class.

The code below shows how easy it is to retrieve preferences from a
configuration. In this example, it gets the location and size of a window
for the current user. Note that there is nothing in the code regarding how
to retrieve these values. It specifies scope when it requests the
Preferences object, and leaves the rest to the plugged adapter.

// Get the Preferences object.  Note, the backing store is unspecified
Preferences preferences = Preferences.userNodeForPackage(this);
// Retrieve the location of the window, default given in 2nd parameter
int windowX = preferences.getInt("WINDOW_X", 50);
int windowY = preferences.getInt("WINDOW_Y", 50);
// Retrieve the size of the window, default given in 2nd parameter
int windowWidth = preferences.getInt("WINDOW_WIDTH", 300);
int windowHeight = preferences.getInt("WINDOW_HEIGHT", 100);

Like Properties, persisting under Preferences is also very easy. The
following example saves a window's size and location for the current user.
It assumes that the variables windowX, windowY, windowWidth, and windowHeight have already been set elsewhere.

// Get the Preferences object.  Note, the backing store is unspecified
Preferences preferences = Preferences.userNodeForPackage(this);
// Save the window location and size
preferences.putInt("WINDOW_X", windowX);
preferences.putInt("WINDOW_Y", windowY;
preferences.putInt("WINDOW_WIDTH", windowWidth);
preferences.putInt("WINDOW_HEIGHT", windowHeight);

On the whole, the Preferences class is no more difficult to use than
Properties. In fact, since the persistence mechanism is transparent, it
could be argued that Preferences is even easier to use. On the other hand,
this isn't necessarily what's needed. The problem hasn't been that the
technology is too difficult to use, but rather that it might be too easy.
Both Preferences and Properties allow and encourage us to carry on in an ad
hoc manner. There is nothing in either to compel us to change our ways.

Towards a Configuration Language

Now, we could all promise to do better in the future, and to treat
configurations with the consideration they deserve. But this doesn't
really address the issue. Good intentions aside, the real problem is we
have nothing with which to replace our bad habits. Stuart Dabbs Halloway
has spent a fair amount of time exploring this topic in a series of articles
entitled "Java Properties Purgatory." Not content to spend his days in
limbo, Halloway proposes a way out.

He begins with a brief overview of properties, and how they are used and
misused by Java developers. From there, he builds a case for a
"configuration language" with the following four elements:

  • Structure: A producer/consumer agreement on how to pass configuration information back and forth. An optional type system allows some validation of the information.

  • Lookup: A way to query configuration information when needed.

  • Scope: Configuration information binds to specific code and data.

  • Metadata: Metadata associated with the configuration information should be exposed in a standard format. This can be exploited by tool builders and automated configuration tasks.

In other words, any well defined, generally applicable configuration model
will incorporate all four elements.

He proceeds to examine JNDI, RMI, and Java Security configurations in light
of these four elements. He finds all three Java technologies lacking. In
terms of structure, each has its own way of passing information
back and forth. For instance, multi-valued properties are delimited
differently under each. Furthermore, they all have different lookup rules
and limited success in addressing scope. In fact, the only agreement among
the three is their total lack of support for metadata.

Halloway then looks to XML as an answer. He believes the basic elements
of the configuration language can be found in the J2EE Web Application
configuration XML. Not only does it have a well defined structure, but it
possess unambiguous lookup rules, and is capable of specifying scope. The
biggest flaw Halloway finds with the model is that it is not generic. It
works well for its purpose, but is too domain-specific to meets the needs of
a general configuration language.

The series ends with Halloway's first cut of a design for what he calls a
configuration interface. He presents five objectives that he feels will
move us towards a better configuration language.

  • Generalize the Preferences API's notion of scope to hook in arbitrary providers.
  • Make XML a first-class citizen in the Preferences API.
  • Explicitly permit some backing stores to be read-only.
  • Add metadata support to the Preferences API.
  • Provide an auditing mechanism to track where configuration information comes from.

The generalization of the Preferences API to support arbitrary providers is
an important feature. Not too long ago, I was peripherally involved in a
project where the developers needed to unify several legacy configurations.
These configurations were persisted in a hodge-podge of backing stores
including properties files, XML documents, and even database tables. The
developers successfully completed the project by migrating all existing
backing stores into one, and creating a single administration console.
However, this only worked because the developers owned the legacy
configurations, and were able to migrate them into one shared source.
Arbitrary providers for the Preferences API would allowed developers to
leave third-party backing stores in place, and write wrappers to plug them
into a single administration console.

As for making XML a first-class citizen, the Preferences class currently
supports the import and export of configuration information as XML.
Halloway would like to extend this by granting direct access to the
underlying data as an XML document. In other words, Preferences should
allow the developer to get and set an XML document.

Metadata support is perhaps the most interesting feature in Halloway's
design. Configurations need to be able to enumerate their properties and
operations. Tools, such as an administration console, could query a
configuration, and display its properties and make use of its operations,
much like an IDE uses reflection to probe a class to built lists of methods
and parameters.

Third Time's a Charm

Much of what Halloway calls for already exists in Java Management Extensions
(JMX). JMX is the third alternative to building configurations in Java. It
is also the third attempt by Sun to create a more robust and scalable
configuration management system than what is found in either the Properties
or Preferences classes. The two earlier attempts were Java Management API
(JMAPI) and Java Dynamic Management Kit (JDMK).

The essence of JMX is the MBean class. An MBean represents a managed
resource. It advertises the configuration attributes and operations of the
resource, and makes them available to another system. The MBean may be
static or dynamic, meaning of all the attributes and operations are known
beforehand, or they may be assembled and modified at runtime. MBeans are
registered as agents with another system, which is typically some type of
centralized administration console. An MBean may reside on the same system
alongside the console, or it may live on a remote node. One of the
beauties of JMX is that transport is completely hidden. Neither the console
nor the MBean is aware of any intermediate protocol. This makes JMX ideal
for distributed environments, which in turn leads to the perception that it
is an enterprise-level technology, although there is nothing to prevent the
use of MBeans locally.

Creating a standard MBean involves little more than following a few naming
conventions. A standard MBean for a given resource is defined by a Java
interface named MyResourceMBean and a Java class, MyResource,
that implements the MyResourceMBean interface. For instance, the
MBean interface and implementation for the sample MyService resource appears
below.

public interface MyServiceMBean {
    void start();
    void stop();
    Integer getConnectionPoolSize();
    void setConnectionPoolSize(int size);
}

public class MyService implements MyServiceMBean {
    private int connectionPoolSize;

    public void start() {
        // Starts the service
    }

    public void stop() {
        // Stops the service
    }

    public Integer getConnectionPoolSize() {
        return new Integer(connectionPoolSize);
    }

    public void setConnectionPoolSize(int size) {
        // adjust actual connection pool, then hold onto new size
        this.connectionPoolSize = size;
    }
}

Making use of the MBean involves little more than registering it with an
MBeanServer, as shown in the code fragment below.

.
.
MBeanServer mbs = MBeanServerFactory.createMBeanServer();
MyService myService = new MyService();
ObjectName myServiceON = new ObjectName("domain:id=MyServer")
mbs.registerMBean(myService, myServiceON);
.
.

The key to appreciating JMX lies within this registration process. As an
MBean is being registered, the MBeanServer builds an MBeanInfo object. MBeanInfo contains the metadata that describes the configuration interface
of the managed resource. In other words, MBeanInfo holds a list of all
of the MBean's attributes and operations. In the case of a standard static
MBean, MBeanInfo is built using introspection during registration. The
MBeanInfo object for the sample MyService would contain two operations,
start and stop, and one attribute, connectionPoolSize.

An alternative to introspection is to have the server directly ask the MBean
for MBeanInfo. This is the essence of a dynamic MBean. The MBean
itself remains in control of what it reveals to the server. Not only does
the MBean remain in charge, it is also able to supply much more information
to the server than the server would be able to glean through simple
introspection. In particular, the MBeanInfo structure allows for a much
richer description of the MBean's attributes, operations, and notifications
than would be possible if the server were limited only to introspection.

It is through the MBeanInfo object that JMX primarily addresses the four
elements of Halloway's configuration language. MBeanInfo brings a uniform
structure, lookup method, and set of metadata to all configurable resources. Although scope remains a problem, properties are by their very nature global, and there is very little that can be done to eliminate this.
Fortunately, it is arguably the least problematic element in Halloway's
configuration language. Especially if the properties are being managed by
a robust, fully developed system such as JMX where the developer needs to go
well out of the way to circumvent the existing mechanisms.

It is the uniformity of structure, lookup, and metadata of JMX that yields
the most promise. It is through these features that tools may be built, and
the implementation of an application's configuration can become a true part
of the design and development process, rather than an afterthought.

Afterthoughts

This article began as a simple warning regarding the use of properties as global variables. However, as I
investigated configurations, and played around with other ideas, a different
theme began to emerge. Basically, configurations do matter. In many cases,
they are the first impression a user has of a given application. How
smoothly configurations go has a lot to do with how well the application is received.
Furthermore, the methods we use to develop our configurations have
non-obvious costs associated with them. Some methods encourage a laissez faire approach. Others require us to be a bit more considered. The more ad hoc the process is, the higher the long-term costs associated with
it. Finally, some methods can be better leveraged by development tools than
can others. Once the tools are in place to exploit this, the design and
implementation of an application's configuration can become a true part of
the development cycle.

Configurations appears to be a largely unexplored area of application
development. I look forward to delving into it a bit deeper, and hope to
publish at least one more article presenting a simple development tool that
begins to exploit the metadata features of JMX.

References:

Craig Castelaz is a full-time husband and father who programs, teaches, and writes in his second full-time job.
Related Topics >> Programming   |