Background
This article details out configuration management in an enterprise application setup with common configuration store.
Any large enterprise application has many moving blocks. They all need to be configured for a proper working of the application. As the application size increases or for scalability, the same configuration has to be repeated in different applications. For most applications, once the configuration has been changed, the application needs to be restarted.
This article is more of a discussion in which I will try to have a conversation into my thought process.
It should be noted that the intent of this article is not to provide a solution to enterprise configuration, instead, it is to let the reader know of the challenges one may face and a set of possible solutions, the designer of the application will then have a choice.
Introduction
This article explores a technique in which we will develop a mechanism for configuration management in which:
- There is centralised location for keeping common configuration elements
- Mechanism for keeping instance specific configuration
- A mechanism (and if possible a framework) for instantiating classes such as web service, WCF client or servers and others which are configured using app.config / web.config
- A mechanism for caching the configuration which is stored in the common configuration store.
- A common code base to read configuration
- How to deploy this common codebase.
Let's try to come up with options / ideas for each of our design goals, see what else we need to read up and to achieve these design goals.
Doing this is a lot of effort so work on this technique if and only if you have a large application with many moving parts. If you believe that your application will in future grow into something large, then read the section on planning for scalability today in this article.
If the next section is very confusing, don't worry, we will get into much more details.
Why Do This
This article has been written for applications which have multiple instances installed in the eco system. It is helpful if and only if you need load balancing and repeating configuration across multiple sites. If you use this application, you will not have to update multiple locations with configuration.
If you have a simple web application or service with few components, this technique is not for you.
The Ideas
The challenges and ideas for the stuff we are about to face - This section details out the ideas of how to solve the requirements stated above. We are not yet going into solution mode. All we are doing is gathering all the challenges we are going to face.
- Centralised location for common configuration elements - This is rather trivial, we should create a separate database in which we will keep these configuration elements. Once out of the idea stage, we will detail out a generic schema for this task.
- Mechanism for keeping instance specific configuration - This is rather debatable, we can think of two ways to do this, one we could modify the central database with some additional fields to identify if the configuration is global or for a central server, the other is keeping instance specific information in regular app.config / web.config files. If we do keep them in .config files, then for a large installation, we need to come up with an installation utility. My preference is to use the central database for this task.
- Mechanism to load configuration - There will be two kinds of configuration items you will face, one which is defined by your business logic such as SMTP server name required by your .NET component or may be the maximum grid size. These will be simple as rather than reading a key value pair or reading from a custom configuration section, you will have to read from the database, the next are components which are provided by .NET such as web service proxy class or WCF configuration. These will be more challenging. We require a central location to get instances of these classes which create an instance that will configure these elements before returning an instance. For example, if we want an instance of web service proxy, we need to set the URL of the web server and these have to be done at a custom location.
To do these, we need to provide a set of wrapper factories and only use them to create instances of the objects. An example of this factory method could be:
WebServiceProxyClass GetInstanceOfWebServiceProxyClass(string webServiceId)
{
}
This wrapper factory is essentially a factory.
- Mechanism for Caching configuration - To have maximum throughput, the configuration needs to be cached by the client of the configuration, not by the configuration provider. This means that if our application required a Windows service and a web site, both these applications will have to cache this configuration and provide a common API to access this configuration element. This caching will provide better performance, however it not an explicit requirement. Once caching is in place, things like cache expiry and related polices will come into play. In this article, we will use the Microsoft enterprise library for the required caching support. The details of this block may be obtained from here.
- Common codebase to read configuration - Once you have actually planned using this centralised configuration management, it is required that the configuration be read from a common class so that is the mechanism of reading the configuration changes we don't go for a toss. This idea is to have a codebase in which we use code like
ConfigManager.GetConfig["KeyName"]
or ConfigManager.MyTestKey
. - How do we deploy this common code base - This final question may sound irrelevant, however, it is of great importance, we are making a policy decision here. There are two ways to deploy the common code base we may deploy. One is to include it as an in-process DLL - this is fine as long as only one application with multiple components is going to use the configuration, on the other hand, what if you want to make this configuration management an enterprise applications (please note the plural) configuration tool. What if multiple applications will get their configuration from this application, for many enterprise applications for consistency configuration data like SMTP server or number of rows in a grid may be stored at a central location. In such a scenario, we will need to graduate our little utility into an Enterprise configuration service. We need to ask ourselves if this is an overkill or whether we will face this in future, what if we want to keep this deployment option open today and for now, just do the bare minimum? Well WCF comes to the rescue. This article on WCF null proxy allows in process execution of a WCF request, thus allowing quick work today but extendibility in future.
So let's sum up what all we need to learn here to go ahead with this article:
- How .NET applications are configured
- The design pattern of Factory and wrapper (we only learn about wrapper for the intent even though the code here will be essentially a factory)
- Enterprise library caching block, you may choose to read about another caching provider or write your own
- WCF and WCF deployment options
Planning for Scalability today - There is a possibility that the business case you have today doesn't justify such a big bang approach but you to protect yourself against changes in future. To do that, you only need to do one thing today, vis, build a common class through which you will access all your configuration. This means there will be no line of code like ConfigurationManager.ApplicationConfiguration["SampleKey"]
in your business classes, there should be in some custom configuration classes. You will however not be shielded against the default configuration providers for WCF or Web services and others.
The Logical Architecture
With all this in mind, let's jump into the logical architecture of this application. We will try to design it with purity in mind and keep any use case specific task to a minimum. We will define the various components and try to map them to six criteria we have set above.
Application A / B - This is any .NET executing assembly in a given process space. It could be a Windows service, a web site or any independently executing component.
The application has many components which require the use of the configuration. The application has a configuration cache the job of this component is to cache the configuration items for the application. This will result in better performance. This cache will call the config API to obtain the values for configuration, it will provide necessary logic to add site specific information.
The application also has a wrapper factory. This a common component which creates instances of objects which require the use of configuration cache. Some examples are creation of database connections, WCF or web service proxy class, WCF hosting, etc. The idea is that we will use this class to get the required instance.
For example, SqlConnection sqlConnection = WrapperFactory.GetConnection(Id);
Configuration Access API - This is a standalone component which will provide us with the configuration values. This will be the data store that the configuration cache will access. This is the location which is now replacing the ConfigurationManager
class of .NET and the only reason we have put in the cache is to ensure that we have a high performance application.
Configuration DB - This is the actual data store for all the configuration and is replacing web.config or the app.config. It will contain the schema for storing both global and site specific information.
The Design
Usually, when I am trying to design something, I have two approaches in mind, one I come up with the E-R model or the database model and build on that. On other occasions, I try to come up with the object model / domain model and work from there. In this scenario, I will come up with the object / domain model, the reason for this is that the nature of this application is service driven and not storage of data.
The most important interface and by virtue of which other things that will be driven in this application will be the configuration access API. The database schema will easily flow from it. The cache will always be a no brainer as it will be a wrapper on this API to ensure fast access, while the wrapper factory quite challenging is not the heart of this application. The wrapper factory exists only to provide instance of some classes which otherwise would have been.
The Configuration API interface - What do we expect from this configuration API?
We expect that we will pass it a site name and application name and it will return us:
- All global configuration across sites and applications
- All global configuration across applications
- All local configuration for the site
- All global configuration for the application
Finally, as a consumer of this information, I am not in the fact that some configuration is global while other stuff is for me, all I want to know is the entire configuration which affects me.
What will this configuration information be?
- Simple key value pairs
- Key value pairs which are ideally stored as a part of a custom configuration section but have been decomposed. We, of course, need a mechanism to group them together and yet keep things generic.
So essentially, the entire configuration can be considered as key - value pairs which need to be grouped logically. A logical group is a bunch of key value pairs which are arbitrarily grouped from the API point of view. The user of this information however needs this grouping to properly instantiate the consumers of this configuration.
With this in mind, let's try to define the Entity
object (or the value object or the data transfer object or the DTO, whatever you may call it) to capture this information along with the methods on the interface to access a DTO.
To achieve this, let's describe a bunch of definitions which will be used in our object model.
- Site - A site is an instance of an application executing on a machine in a separate application domain.
- Application - An application is an executing assembly hosted in a process space.
- Machine - A machine is a physical object on which one or more applications are executing.
Overriding Criteria
In the system machine, application and site level configurations can be defined for any executing assembly, in case of a collision machine level configuration is overridden by application level and application level is overridden by site level configuration. Consider the example:
Machine Configuration
Key | Value |
Key1 | M1 |
Key2 | M2 |
Key3 | M3 |
Key4 | M4 |
Application Configuration
Key | Value |
Key1 | A1 |
Key2 | A2 |
Key5 | A2 |
Key6 | A3 |
Site Configuration
Key | Value |
Key1 | S1 |
Key3 | S2 |
Key5 | S3 |
Key7 | S4 |
Key8 | S5 |
The keys available at the application are:
Key | Override order | Value |
Key1 | M1 <A1 <S1 => Site overrides both machine and application | S1 |
Key2 | M2 <A2 => Application overrides machine | A2 |
Key3 | M3 < S2 => Site overrides machine | S2 |
Key4 | M4 | M4 |
Key5 | A2<S3 => Site overrides application | S3 |
Key6 | A3 | A3 |
Key7 | S4 | S4 |
Key8 | S5 | S5 |
The design should support a custom override criteria so that future changes are simple.
Server Side Object Model
Class Details
ConfigurationElement
- This class contains essentially a bunch of key value pairs which form the values for various attributes of configuration. The entity also contains the PropertyName
property which may be used to identify the property being set. A client may use reflection/introspection or indexers (where possible) to use this. The property OverrideType
may be used to define what has given this field its value. ConfigurationGroupData
- This class is used to logically represent a group of keys associated with one of the components for a site. Examples can be a given WCF component, a webservice
, a Database connection string
, some keys in appSettings
and others. ConfigurationEntity
- This class contains all the configurations for a site. The site is identified by a site ID. This site id is a unique combination of application and machine.
These class details / object model form the essential data contract for our application.
The next section contains the interface to obtain configuration from the data store.
Based on the description of a site vis machine + application name, we return the configuration information. The data is overridden based on the override criteria. If a machine is not specified or not found, then configuration for the given application should be sent back.
So in principle, we get all the keys for site which can be machine level, application level or site level keys and check which overrides what, create a ConfgurationEntity
and send it back.
The IConfigurationStore
in principle forms the operation contract of our work.
Database Design
This section details out the database schema for the application. We will not dive deep into every column and its purpose. I am sure the audience is aware of IsDeleted
flags for soft deletes or the purpose of CreationDateTime
and LastUpdateDateTime
.
- Deployments - This table contains the names of various units of deployments such as definition of applications, machines or a combination of machine and application vis sites.
Some examples:
Machine Name | Application Name | Comments |
Server1 | | This configuration is sent back whenever Server1 is requested in context of any application. |
| Application1 | This configuration is sent back whenever Application1 is requested in context of any application. |
Server2 | Application1 | This is sent back whenever Application1 is requested by Server2 . The keys of this site override the ones in Application1 . |
- Components - Components are any separate logically grouped bunch of keys in an executing application. Some examples are WCF related keys, Web service related keys, DB related keys and others. We give a loose definition of component type - this may be WCF while component name is an instance of component type, for example, a WCF server keys for the identity service of DB connection keys for the reporting server. The component type tells what is the exact area of configuration while the instance is the specific deployment. It is similar to class vs. instance of class.
- Keys - This is the actual bunch of keys associated with a component. For example, with WCF instance, it may contain the URL, time out and others. With each key, we also store a property.
- KeyValues - These are the actual values for a given key. They can be overridden on Application and Site level.
- ComponentDeployment - This is a logical grouping of components for a deployment. For example, a web application contains a bunch of WCF deployments, one database deployment and some details of http runtime details.
The schema given above is rather generic, in case however for operational efficiency, you may create table structures which is structured for various entities.
Override Algorithm
As a part of design, we also give here the overriding algorithm as under. This is just one of the possible implementations of the delegate the consumers of this framework may choose to implement their own.
public bool IsOverrideImplementation (OverrideType oldValue, OverrideType newValue)
{
return newValue > oldValue;
}
This concludes the server side of things, let us debate a bit on the client side of things which include the caching of the keys and a bunch of factories which are required to instantiate a class.
Caching
The ConfigurationEntity
must be cached in the client application. We need a mechanism for cache refresh. It is highly recommended that before we go ahead, we should read this article.
The reason we propose this as a caching tool instead of .NET cache is because it allows caching in any kind of .NET application and not just web application. Further, this tool allows expiration scavenging and other features.
Factories
On the client end, a whole bunch of factories have to be created. These will be used to instantiate the elements. Why do we need these?
Consider the example when we wish to use a web service, we create an instance of the proxy
Service = new Service();
Internally, this constructor will do a couple of things:
- Assign the URL to access the web service.
- Assign the format of requests such as Soap or Http Post.
- Set the time outs and many other properties.
The job of this factory will be to create an instance and assign these values from the cache.
If the application has too many class types and classes which get affected by this configuration, we may use an abstract factory pattern instead of factory pattern.
Contents of the .config File on the Client
We should understand and acknowledge that not everything can be moved to the DB structure. We will have to maintain at least a WCF end point to access the configuration store for an out of process configuration store. Things like details of httpModules
, debug/release mode and other settings which need to be loaded early on need to be kept in the .config file.
A Sequence Diagram Is Called For
Let's try to understand the overall flow how a certain application will be configured in the sequence diagram given below.
There is not much to say here. Any class which needs to be instantiated requests the wrapper factory to create the instance. The wrapper factory has all the logic to create instance except for config values. These values are obtained from the cache. The cache fills its values from the WCF API. We fill the entire cache in one go.
At this point of time, I intend to stop, as this article is getting too long. At some time in future, I will share the sample implementation.
Finally, if you find this article helpful, please rate it...