Configuration Manager - coolsamson7/inject GitHub Wiki

Overview

The purpose of this configuration mechanism is to provide a uniform api to access configuration values and at the same time allows different implementations to act as providers - process info, plists, etc. - for concrete values. In addition to static values, dynamic configuration values are supported by letting the programmer install callbacks that react on changes of particular values.

Let's look at an example first:

let configurationManager = ConfigurationManager()
configurationManager.addSource(PlistConfigurationSource(name: "Info")) // Info.Plist

let url = try configurationManager.getValue(String.self, key: "url")
let port = try configurationManager.getValue(Int.self, key: "port", defaultValue: 8080) // a default value

As you can see, the int value is automatically covered to the requested type ( if possible )

Every configuration value is defined by several properties

  • namespace an optional "." separated path or ""
  • key the configuration name
  • type the configuration type
  • value the configuration value
  • dynamic a boolean property that specifies if the value is considered static or dynamic

The namespace and key properties uniquely identify a configuration item. In addition a property scope - a "." separated path - may be supplied that is also part of the key.

The idea is that when fetching configuration values given a specific scope, the exact key is looked up. If not available all parent scopes are traversed and the first result will be returned. The mechanism allows for inheritance of configuration values and simplifies configuration of similar processes - e.g. nodes in a cluster - by defining common values and overriding values that are bound to a specific environment which would be coded as a specific scope.

Example:

  • scope: "" ( default ) namespace "a.b" key: "key" value: 0 scope: "foo" namespace "a.b" key: "key" value: 1
  • scope "foo.bar" namespace "a.b" key: "key" value: 2

retrieving the configuration value "a.b:key" ( ':' simply separates the namespace from the key) would result in different values for a supplied scope

  • scope="": 0, the default
  • scope="foo": 1
  • scope="foo.bar": 2
  • scope="foo.bazong": 1, the inherited value from scope "foo"

Normally it is treated as an exception if identical configuration value specifications are encountered. As an exception system variables are always allowed to override existing values!

Let's look at the involved protools.

ConfigurationProvider

protocol ConfigurationProvider {
    func getConfigurationItem(namespace : String, key : String) -> ConfigurationItem?

    func hasValue(namespace : String, key : String,  scope : Scope?) -> Bool

    func getValue(type : Any.Type, namespace : String, key : String, defaultValue: Any?, scope : Scope?) throws -> Any
    
    func addListener(namespace : String, key : String,  listener : ConfigurationListener , expectedType : Any.Type, scope : Scope) -> Void
}

defines the protocol that is used to retrieve configuration values.

ConfigurationSource

public protocol ConfigurationSource {
    func load(configurationManager : ConfigurationManager) throws -> Void

    func startListening(configurationManager : ConfigurationManager, seconds : Int) -> Void
    
    var url : String { get }

    var mutable : Bool { get }

    var canOverrule: Bool { get }
}

defines a source of configuration values.

ProcessInfoConfigurationSource

ProcessInfoConfigurationSource is a source that collects process info values ( NSProcessInfo.processInfo() )

PlistConfigurationSource

PlistConfigurationSource is a source that reads a plist file. The constructor is

init(name : String) where

  • name: is the file name excluding the ".plist" which will be looked up in the main bundle

ConfigurationManager

ConfigurationManager is the central class that collects different sources and i used to retrieve values. It implements the interface ConfigurationProvider and the administrative protocol:

public protocol ConfigurationAdministration {
    func addSource(source : ConfigurationSource) throws -> Void
    
    func configurationAdded(item: ConfigurationItem , source : ConfigurationSource) throws -> Void
    
    func configurationChanged(item : ConfigurationItem) throws -> Void
}

and also adds generic functions.