Using the Sample Project
The downloadable sample code
contains a fully functional project that retrieves property values from a central SQL Server database and stores them locally in the computer's registry. The property values stored locally honor the ValidFor
setting in the escPropertyValue
table by updating the local cache from the SQL Server system as specified.
A call to the PropertyValue class's GetValue
shared method functions is the entry point into the system. You only need to pass GetValue
and the name of the property you want to retrieve. The RemoteStorageClient class retrieves the server name, and the tables using the server name can infer the location. The system will return a PropertyValue object for the requested property if that property value can be retrieved from either local or remote storage.
Populating the Tables
Initially, to populate the database tables, create escProperties rows for each property you plan to implement. Do not create a property row with a property ID of zero—wildcards are not supported in the property name. Property values will vary widely from one enterprise to the next, but most facilities are likely to have property labels such as LogFileLocation, DebugLevel, and ErrorReportingWebServiceURL.
Next, create a row in the escModules table for each module you want to configure separately. You will also want to create a wildcard row with a ModuleID of zero. My recommendation is to create a module row for every assembly you run on a machine—not just for executables. This will allow you to add specific configuration information for each piece of software on the system.
Finally, add rows to the escServers and escLocations tables for each server and location in your enterprise. Also add wildcard rows to both of these tables for the reasons described above.
The downloadable code includes a sample client application, named escTest. You can use this client to help understand how the framework operates, and to test new local and remote storage frameworks. You can also use it to verify that the SQL Server database has been configured correctly.
After you have retrieved several values and have verified that subsequent retrievals are being returned from the local cache, open the registry and inspect the local property values. Using a registry editor, navigate to HKEY_Local_Machine\Software\EnterpriseSoftwareConfiguration. You should find sub-keys for each ModuleID used to retrieve property values. Under each ModuleID sub-key, you will find separate sub-keys for each property label whose values have been retrieved. The PropertyValue and ValidUntil registry keys are stored under the property label sub-keys. Figure 4 shows the "Log File Location" property for ModuleID 1003 stored hierarchically in the registry.
|Figure 4. Local Registry Store: The LocalStorageClient_Registry class stores property values hierarchically using the ModuleID and PropertyLabel properties.|
One implication of storing all property values under the module ID is that property values shared by all modules (those with a ModuleID
of 0 in the escPropertyValues
table) will be stored redundantly. In other words each ModuleID
that queries for the same property value will store its own copy. That's an acceptable price to pay, because it has several advantages. In addition to being a very simple way to implement the storage, this storage scheme also simplifies the process of debugging property value storage. Bear in mind that just because a value can be assigned a ModuleID
of zero doesn't mean that all instances of the property value will be the same. The model supports setting a generic property value with a ModuleID
of zero, while still letting selected modules have a specific value for the same property.
You should also note that use of the registry for local cache storage is available only on Windows platforms. Other platforms will require a different local storage solution.
Implementing the Project in Your Enterprise
I have intentionally kept the functionality and source code of the sample project simple to illustrate how the system can work flexibly in any organization. Before you implement it, be sure you're doing it for the right reasons. Remember that the proposed framework is not intended to help cache frequently used application data; in other words, don't consider it as a general-purpose cache. Many of the techniques used to build the framework are borrowed from such caching designs, but you'd have to optimize the design of the local and remote storage clients differently for higher-volume use or for storing larger chunks of data. As described here, the system functions solely as an alternative to managing software configuration via the registry and/or .NET's app.config files.
In addition, you should consider making at least some of the modifications in the following list before deploying the project in your organization:
- It would be useful to create a basic web-based configuration tool to make configuration easier for the end-user. The tool should support the use of wildcards in property value assignments.
- If you do not use SQL Server, you should add a RemoteStorageClient class specific to your central configuration repository. Whether you're modifying the code to use Oracle, the registry, or LDAP, you will find the modification process straightforward, and similar to the client repository modification discussed in this article.
- If your enterprise operates on several different platforms, such as Windows 2000, Windows 2003, or Linux, consider adding an escPlatforms table and a Platform column to the escPropertyValues table so you can support specific property values for each platform. If you do that, remember to link the escServers table to the new escPlatforms table in a manner similar to the link between escServers and escLocations.
- If you operate from a single location, consider removing support for the escLocations table to simplify queries and configuration.
- If you aren't happy with the prospect of using the registry to store locally cached property values, consider adding a LocalStorageClient to support whatever local storage mechanism you wish.
- Consider adding support for more than one type of LocalStorageClient. As mentioned previously, if the bulk of the applications on a machine are ASP.NET applications, it may make sense to use ASP.NET's built-in caching for local storage. If configured properly, you could modify the LocalStorageClient's base class factory method (GetLocalStorageClient) to return a different LocalStorageClient implementation depending on the client application's identity. Keep in mind, using a strictly in-memory local cache will not help in cases where the application stops and restarts. In such cases, the cached values are lost and will need to be reseeded locally.
- Depending on how your applications are configured, you may consider adding an attribute to the escPropertyValues and/or escProperties tables to support "critical" properties. As coded in the sample project, the normal operation of the LocalStorageClient for a property that cannot be refreshed after its ValidFor period ends is to return the expired property value. However, you could alter this so that when a property marked "critical" expires, the system would return a null value when the property cannot be retrieved from the central repository.
- Using your enterprise's standard error reporting framework, add support for logging errors when trying to retrieve values from the repository. Error reporting was not implemented in the sample project to keep the project simple.
- Don't be limited by the perception that a configuration value is generally assigned to a module. While most modules will represent a physical piece of software, it can also represent an instance of a module. For example, if you have a service that loads multiple instances of the same or similar assemblies, then the configuration for the service could instruct to load specific modules—each of the modules it loads reads their specific configuration settings. Extension of this idea will add greatly to the ways this system can be used to configure your enterprise systems.
- You could implement a local Windows service to help by pre-fetching property values before they expire. By retrieving property values before they expire, the client application will seldom experience the additional delay caused when a local property value must be refreshed.
- Using the Windows service described above, it would probably be helpful to probe local storage, removing property values that are more than a month old. These values are probably not used any longer and should be removed from the system
- One problem with caching property values locally is that they become out of date when the master copy of the value in the central repository changes. For example, if the ValidFor property is set to one hour, you will likely have servers running with old values for up to an hour. Therefore, another use for a local service would be to receive notifications when property values should be updated immediately. With such a service, you could reduce the time required to push new central values to all the client systems to mere seconds.
- One weakness in the implementation of the sample project is that when the central database is unreachable, all attempts to retrieve expired property values will block, waiting for the database connection to time out. To keep the local storage client responsive, you should implement a flag in the local cache that prevents unnecessary connections to the database when it is known to be unresponsive. As an example, using the registry version of the local storage client, you could add a flag to the root of the EnterpriseSoftwareConfiguration sub-key to indicate that the central database is unresponsive, along with a time after which the system should try connecting again. In this scheme, all attempts to read from remote storage would return null, meaning that property requests would simply return the version stored in the local cache. The flag would limit attempts to access the central database. For example, assuming you set the flag to one minute, only one client per minute would attempt to access the database while it is marked offline. The first client that successfully makes a central connection and retrieves a property would clear the flag, and normal property refreshes would resume.
As your organization grows, you will almost certainly feel the pain of keeping software configuration properties on multiple servers synchronized. Consider using the project provided in the sample code as a starting point for building your own enterprise-ready software configuration framework, extending it to fit the specific needs of your enterprise.