Objective
The objective of this page is to establish architectural best practices for TopCoder component configuration management, to be reflected in future components and to be factored into existing components.
Status
Working Draft
Requirements
Configuration must be standardized across all components. Configuration for any component X within the catalog should be practically similar to configuration for any component Y.
Default configuration implementation must be defined.
Configuration scope must be defined.
Unit tests should test the component's logic and not integration of the component with other components or system dependencies.
Requirements Discussion
There are many configuration approaches components can use, e.g. Inversion of Control, Bootstrapped, Programmatic, etc. Application development and maintenance are greatly augmented when a single consistent approach can be leveraged across the entire application. Through proper design components can defer the configuration approach to the application. Configuration Management implementation and interfaces should be adequately separated to allow for swapping in new implementations with minimal amount of work.
Lessons learned from use of Configuration Manager (CM)
Components had a hard dependency on CM component which meant even if CM was not used it had to be included at compile and even run time. Even components that allowed for alternate means of configuration typically were hard to use without bootstrapping off of the CM. Configuration parameters were inconsistently defined: hard coded parameters often had different scope modifiers and sometimes parameters names were even configurable. Configuration code often dominated the main class of a component making reading the code a difficult, tedious, and time consuming process. Use of the singleton pattern greatly limited the flexibility in alternate persistence mechanisms. Tight coupling of the configuration persistence implementation, the configuration model, and the client components greatly reduced flexibility. Unit tests typically depend on CM setup instead of stubbing, partially because of the difficulty in efficiently stubbing CM. This makes setup and running of unit tests more difficult and the unit tests less powerful.
Recommendations
Decouple the persistence implementation from the configuration model:
- Configuration API provides a powerful object that models configuration.
- Configuration Persistence provides file based (XML or Java property) persistence component that provides default persistence compatible with existing files.
- Configuration Persistence can also provide support for newer file based features compatible with Configuration API.
- For .NET, Configuration Persistence Manager allows applications to load applications from a persistence store without a hard dependency on the persistence implementation. Current compatible implementations include the file system, and standard .NET Configuration objects. Future implementation could include a database level configuration store.
- Only code that requires into a top level application should load files from persistence. Any component that does not run as a standalone program should NEVER load Configuration API objects from persistence itself, but rather accept the configuration as constructor parameters, properties, or "Configure" methods.
Component designs will be scored for adherence to the following configuration best practices, unless the component design can provide a compelling justification for deviation. If no such justification exists in the design documentation, then reviewers must require that the design be changed to adhere to these practices or if doing so would create a significant negative impact on the design AND a valid justification for the variance exists, that the justification be added to the design
Design Best Practices:
- Components are broken into simple and complex categories
- Simple components do not depend on other components
- Complex components depend on at least one other component
- Simple components shall only support programmatic configuration, i.e. constructor or setter of objects required by the component, consistent with other component design principles, like thread safety.
- Complex components shall support
- At a minimum programmatic configuration as supported by base components.
- Use of Configuration API when such usage significantly enhances the design's quality.
- Programmatic configuration shall use actual meaningful objects and not proxy for these objects with primitives or simple objects that must be translated by the component.
- Components that use Configuration API shall keep all such usage in an appropriate helper class and or method, such as a builder or factory, which leverages the programmatic configuration and handles any string conversion and configuration of dependant components.
- Applications shall define any necessary singleton or wrapper constructs for wrapping shared components or limiting access to the file system via the configuration persistence.
- Only rare cases where a component provides a standalone program (compiles to an exe in .NET or can be run with "java.exe" for Java components) may load configuration from persistence using either .NET Configuration Persistence Manager or Java Configuration Persistence components. All other components must accept Configuration API objects as inputs.
Unit tests for components shall adhere to the following development best practices with respect to configuration, to the extent that more general best practices exist, this should be read to provide clarification to the general best practices, without overruling them. Where it is not possible to provide unit tests that follow these best practices the developer should so note in an appropriate place in the unit tests' javadocs.
Development Best Practices:
- Component unit tests shall decouple the dependence configuration components
- Components unit tests shall decouple the dependence on the configuration (and implementation) of other components
- If multiple configuration approaches, i.e. programmatic and convenience object are supported, then unit test coverage shall include verification of both configuration approaches, but without injecting dependencies.
- If the component uses Configuration API, then the Component Specification should include a snippet of XML code from the configuration file used by the application to configure the component.
Nesting of Configuration
Complex Components that use and configure other simple or complex components will keep their child component's configuration as a child of their own configuration. For example, consider the two components "Widget Service" and "Widget Persistence." Widget service uses object factory to create an instance of Widget Persistence, and then calls Widget Persistence's "void Configure(IConfiguration config)" method with the code: "persistenceInstance.Configure(myConfig.GetChild(PERSISTENCE_CONFIG_KEY));"
This will give the child service the correct configuration. Under no circumstances should a component assume that the configuration object passed to it is a parent of its own configuration, and immediately try to retrieve a child object of its own configuration. While this may seem obvious, there are instances in the catalog where both a parent and a child take nesting into account, producing an unnecessary layer of configuration.
Additionally, the parent object should not read a property to give it the name of the child configuration (e.g. "persistenceInstance.Configure(myConfig.GetChild((string) myConfig.GetSimpleAttribute(PERSISTENCE_CONFIG_NAME)));" is incorrect). The child configuration name needs to be a known string set as a "const string" or "final static String" of the parent class.
Open Issues
Definitions
There is an issue with Configuration API. Suppose that I configure a class with ConfigurationObject. This class needs the persistence interface that it's constructed through Object Factory. I must pass a ConfigurationObject to Object Factory. Where do I retrieve the ConfigurationObject for Object Factory? from the ConfigurationObject of the original class? If so then in the entire configuration I must link the CofigurationObject for Object factory in all ConfigurationObjects of all classes that need ObjectFactory.