Previous Up Next

Chapter 7  Limitations of the "uid-" Prefix

7.1  Introduction

Let’s assume we want to write an application that creates objects based on information provided in scopes in a configuration file:

object_1 {
    type = "Person"; # The type of object to create
    ... # Other name=value pairs specify values for instance variables
}
object_2 {
    type = "Car"; # The type of object to create
    ... # Other name=value pairs specify values for instance variables
}

When editing the configuration file to add more object_<int> scopes, it will be tedious to keep track of which numbers have already been used. To avoid this problem, we might decide to use the "uid-" prefix instead.

uid-object {
    type = "Person";
    ...
}
uid-object {
    type = "Car";
    ...
}

At first sight, this appears to be an improvement. However, the "uid-" prefix has some some subtle limitations that can affect the ergonomics of a configuration file. This chapter explores those limitations, and suggests an alternative approach that, sometimes, can work better.

To make the issues discussed in this chapter more concrete, I base the discussion around the design of a hypothetical application for monitoring security devices—such as burglar alarms and security cameras—that can be connected to a computer network.

Each scope in a configuration file specifies details for a security device: (1) its network address as a string of the form ip-address:port; and (2) a brief description of its physical location, for example, “front entrance” or “loading bay”. When the application examines a configuration scope, it creates a “device driver” object based on information in the scope, and adds that object to a collection. To monitor the status of security devices, the application simply iterates over the collection and invokes an operation on each object that queries the status of the corresponding security device.

In this chapter, I discuss two different approaches that can be taken with Config4* to store details of each security device. The first approach illustrates limitations of the use of the "uid-" prefix. The second approach avoids using the "uid-" prefix and, in doing so, avoids its limitations.

7.2  Approach 1: With "uid-" Entries

Figure 7.1 shows how a configuration file might use the "uid-" prefix to store details of different types of security devices.


Figure 7.1: Security configuration with "uid-" entries
uid-camera {
    network_address = "192.128.42.006:5000";
    location = "front entrance";
};
uid-camera {
    network_address = "192.128.42.009:5000";
    location = "loading bay";
};
uid-burglar-alarm {
    network_address = "192.128.42.021:5000";
    location = "loading bay";
}

The format of the configuration file in Figure 7.1 is straightforward: there is an uid-camera scope for each camera on the network and, likewise, an uid-burglar-alarm scope for each burglar alarm on the network.

Let’s assume that the security monitor application wants to display a warning message about a malfunctioning camera. Several options come to mind for the format of such a message.

The first option is for the message to identify the camera by reporting the expanded form of its uid-camera name, for example, "Camera ‘uid-000000042-camera’ is malfunctioning". However, such a message is not user-friendly: a user would have to laboriously search through the configuration file for the 43nd occurrence of the "uid-" prefix to identify the relevant camera.1

A second option is for the message to identify the camera by reporting one of its attributes, such as its location: "The camera at location ‘front entrance’ is malfunctioning". Such a message seems to be user-friendly, but it unambiguously identifies the relevant camera only if the location attribute has a unique value. This may not be the case if there are several cameras placed in the same location (perhaps for redundancy purposes, or perhaps to provide different views of the same location). If there are no existing attributes that are guaranteed to be unique, then you could introduce one, such as id shown in Figure 7.2.2


Figure 7.2: Security configuration with id attributes
uid-camera {
    id = "1";
    network_address = "192.128.42.006:5000";
    location = "front entrance";
};
uid-camera {
    id = "2";
    network_address = "192.128.42.009:5000";
    location = "loading bay";
};
uid-burglar-alarm {
    id = "3";
    network_address = "192.128.42.021:5050";
    location = "loading bay";
}

The need to introduce the id attribute in Figure 7.2 is ironic because the whole point of the "uid-" prefix is to avoid users having to invent unique identifiers. By introducing the id attribute, we reintroduce problems that the "uid-" prefix was intended to avoid. In particular, users must ensure that each id has a unique value. This may not be a significant problem if there are just a handful of scopes in a configuration file, but it can become a problem if a configuration file contains hundreds or thousands of scopes. This problem can be eased somewhat if the value of id can be an arbitrary string rather than, say, just an integer. In this case, a user might set a device’s id to be its location suffixed by a number. For example, three devices at the front entrance might have id values: "front entrance: 1", "front entrance: 2" and "front entrance: 3", while two devices at the loading bay might have id values: "loading bay: 1" and "loading bay: 2". Such a convention can help reduce the difficulty of ensuring unique id values for each of hundreds of devices (assuming there are only a handful of devices at each location). If this approach is taken, then the application can produce messages of the form, "Camera ‘front entrance: 2’ is malfunctioning". This is more user-friendly than "Camera ‘uid-000000042-camera’ is malfunctioning".

The schema definition for the configuration file shown in Figure 7.2 is straightforward, and is shown in Figure 7.3.


Figure 7.3: Schema for the configuration file shown in Figure 7.2
String[] schema = new string[] {
    "uid-camera                 = scope",
    "uid-camera.id              = string",
    "uid-camera.network_address = string",
    "uid-camera.location        = string",

    "uid-burglar-alarm                 = scope",
    "uid-burglar-alarm.id              = string",
    "uid-burglar-alarm.network_address = string",
    "uid-burglar-alarm.location        = string"
};

It is important to note that the Config4* schema language does not provide any way to ensure that each id variable has a unique value. Because of this, a developer implementing the security-monitoring application would need to write code that checks for clashes in the values of the uid-camera.id variables. You can implement this code as follows. First, you create an empty hash table that will provide an id → scope mapping. Then, you populate this hash table by iterating over all the uid-camera scopes to obtain the value of the id variable within each scope. Before adding each id and scope to the hash table, you check if the hash table already contains an entry with the same id value. If it does, then you report the clash as an error.

7.3  Approach 2: Without "uid-" Entries

As I explained in Section 7.2, using the "uid-" prefix in the configuration file for the security-monitoring application was not as beneficial as we might have hoped: we still had to introduce an artificial id variable inside each scope. Since the "uid-" prefix is not providing as much benefit as we would like, we might decide to avoid its use altogether, as shown in Figure 7.4.


Figure 7.4: Security configuration without id attributes
camera {
    1 {
        network_address = "192.128.42.006:5000";
        location = "front entrance";
    }
    2 {
        network_address = "192.128.42.009:5000";
        location = "loading bay";
    }
}
burglar-alarm {
    1 {
        network_address = "192.128.42.021:5050";
        location = "loading bay";
    }
}

This configuration file foregoes both the "uid-" prefix and the id variable. Instead, the configuration file uses a unique scope name for each camera or burglar alarm. The user has the responsibility of ensuring that there is no clash of these scope names, but this is hardly more of a burden than ensuring there was no clash of the values of id variables. Figure 7.5 shows how this configuration file can be written in an semantically identical but more compact and intuitive syntax.


Figure 7.5: More concise security configuration without id attributes
camera.1 {
    network_address = "192.128.42.006:5000";
    location = "front entrance";
};
camera.2 {
    network_address = "192.128.42.009:5000";
    location = "loading bay";
};
burglar-alarm.1 {
    network_address = "192.128.42.021:5050";
    location = "loading bay";
}

A security-monitoring application that uses this type of configuration file can report a problem with, for example, "The ‘camera.2’ device is malfunctioning", which seems clear enough. Thus, in terms of usability, this approach is arguably better than the first approach (discussed in Figure 7.2).

Of course, the scope names for individual devices do not have to be integers, so a user is free to use more meaningful names:

camera.front-entrance-1 {
    ...
};
camera.loading-bay-1 {
    ...
};
burglar-alarm.loading-bay-1 {
    ...
}

The main drawback of this approach is that the Config4* schema language is not flexible enough, by itself, to validate the contents of such a configuration file. Instead, a developer must perform schema validation in a piecemeal manner, as I now discuss.

First, the developer uses the schema shown in Figure 7.6 to validate the top-levels of the configuration file. Notice that this schema ignores the scopes nested within the camera and burglar-alarm scopes.


Figure 7.6: A top-level schema for the configuration in Figure 7.5
String[] schema = new string[] {
    "camera = scope",
    "@ignoreScopesIn camera",

    "burglar-alarm = scope",
    "@ignoreScopesIn burglar-alarm"
};

Second, to validate the details of each camera, the developer calls listFullyScopedNames() to obtain the names of the scopes nested within camera.

String[] names = cfg.listFullyScopedNames("", "camera",
                      Configuration.CFG_SCOPE, false);

The developer can then validate each of those scopes with the schema shown in Figure 7.7.


Figure 7.7: A schema for a camera scope in Figure 7.5
String[] schema = new string[] {
    "network_address = "string",
    "location        = "string"
};

The developer can validate burglar alarms scopes in a similar manner, that is, by calling listFullyScopedNames() to obtain a list of the scopes nested within burglar-alarm, and then validating each of those nested scopes.

7.4  When to use the "uid-" Prefix

In Section 7.2, I explained why you might not want to use the "uid-" prefix. This raises the question: is the "uid-" prefix ever useful? In particular, under what circumstances can the "uid-" prefix achieve its intended goal of eliminating the burden for users to create identifiers with unique values? Unfortunately, I cannot give a definitive answer to that question. This is because the "uid-" prefix was introduced relatively late in the development cycle of Config4*, so I have not had the opportunity to use it sufficiently often to feel confident that I know all its strengths and weaknesses. However, the recipes configuration file shown in Figure 7.8 provides two examples of when the "uid-" prefix can be used without problems.

  1. The "uid-" prefix can be used on the name of a scope if that scope naturally contains a variable whose value: (a) is guaranteed to be unique across all similar scopes; and (b) is suitable for use in human-readable messages. For example, it is reasonable to require each uid-recipe scope to contain a name variable whose value is unique and meaningful to humans. An application can unambiguously report a problem about a specific uid-recipe by referring to the value of its name variable.
  2. The "uid-" prefix can be used on the name of an item (that is, a scope or variable) if the item is, essentially, anonymous. This is illustrated by the uid-step variables within each uid-recipe scope.

Figure 7.8: File of recipes
uid-recipe {
    name = "Tea";
    ingredients = ["1 tea bag", "cold water", "milk"];
    uid-step = "Pour cold water into the kettle";
    uid-step = "Turn on the kettle";
    uid-step = "Wait for the kettle to boil";
    uid-step = "Pour boiled water into a cup";
    uid-step = "Add tea bag to cup & leave for 3 minutes";
    uid-step = "Remove tea bag";
    uid-step = "Add a splash of milk if you want";
}
uid-recipe {
    name = "Toast";
    ingredients = ["Two slices of bread", "butter"];
    uid-step = "Place bread in a toaster and turn on";
    uid-step = "Wait for toaster to pop out the bread";
    uid-step = "Remove bread from toaster and butter it";
}

In the security-monitoring application, the uid-burglar-alarm and uid-camera scopes contain a network_address variable, whose value is unique. Because of this, the application could unambiguously identify a device with a message such as, "The camera at network address 192.128.42.006:5000 is malfunctioning".

However, a network address is a low-level piece of information, and a security-monitoring application might prefer to identify a device with a more human-friendly description. It is this desire for a meaningful to humans, unique identifier that motivates either: (1) the introduction of the id variable in Figure 7.2; or (2) finding an alternative to use of the "uid-" prefix, as shown in Figure 7.5.

7.5  Summary

In this chapter, I have explored how an application might have a configuration file that contains a separate scope for creating each of an arbitrary number of objects. The obvious approach is to use the "uid-" prefix on the names of scopes. For example:

uid-camera { ... };
uid-camera { ... };
uid-camera { ... };

Unfortunately, this approach works well only if the scopes naturally contain a variable whose value: (1) is guaranteed to be unique across all similar scopes; and (2) is suitable for use in human-readable messages. If this is not the case, then you may find yourself introducing an artificial id variable inside each scope:

uid-camera { id = "..."; ... };
uid-camera { id = "..."; ... };
uid-camera { id = "..."; ... };

If you find yourself in that situation, then it may be better to forego the use of the "uid-" prefix, and instead employ a unique identifier as a sub-scope:

camera.id1 { ... };
camera.id2 { ... };
camera.id3 { ... };

1
The counter for uid entries starts at 0, so 42 is the 43rd occurrence of a uid entry.
2
Actually, the network_address attribute is likely to be unique, but I will ignore that for the moment because I want to focus on what can be done if there are not any unique attributes.

Previous Up Next