Configuration Blues Blog

Version 2

    {cs.r.title}




    Have you ever noticed how some applications seem to configure themselves? I don't mean that they aut o-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 appli cation 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 theProperties class seems almost inevitable.

    For all its inevitability, though, this behavior does have its problems. Since configurations are so easy to implement using theProperties 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 fairepractice 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 anOutputStream 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 underPreferences 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 thatPreferences 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 Propertiesallow 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, thePreferences 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 orPreferences classes. The two earlier attempts were Java Management API (JMAPI) and Java Dynamic Management Kit (JDMK).

    The essence of JMX is the MBean class. AnMBean 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 MBeanfor a given resource is defined by a Java interface namedMyResourceMBean and a Java class,MyResource, that implements theMyResourceMBean interface. For instance, theMBean interface and implementation for the sampleMyService 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, theMBeanServer 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 theMBean's attributes and operations. In the case of a standard static MBean, MBeanInfo is built using introspection during registration. The MBeanInfoobject 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 MBeanitself 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, theMBeanInfo 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:

      
    http://today.java.net/im/a.gif