Skip to content
Aviv C edited this page Oct 27, 2017 · 7 revisions

ConfEagerJS Documentation

ConfEagerJS Build Status at Travis CI

ConfEagerJS is an eager, strongly-typed configuration library for JavaScript, designed to maximize runtime safety, while maintaining lightweight, easy integration and dynamic nature.

Comparability Note: ConfEagerJS uses Smoke Screen library to define and validate object schemas and types at runtime. This requires usage of EcmaScript decorators. While EcmaScript doesn't officially support decorators yet, the examples below are implemented in TypeScript, but may also be implemented in any other way that compiles decorators.

Getting Started

ConfEagerJS library provides 2 main capabilities: defining strongly typed configuration object shcemas, and then, persistently consuming them.

The first of which is done via Smoke Screen library interface. Quick examples may be found below, but further reading at the project page is encouraged.

The second, persistently consuming the data, is ConfEagerJS specialty. This is done via ConfEagerSource class. To show this in action, let's start with a simple example in which we would like to read and validate 2 configuration properties from a YAML file.

First, let's define the properties we would like to read by declaring our Configuration Class, which contains the two Configuration Properties:

class LocalConfiguration {

    @exposed({type: PropertyTypes.boolean})
    readonly enableLogs: boolean;

    @exposed({type: PropertyTypes.string})
    readonly logDirectory: string;

}

This declares the properties we would like to read, their type and their property names. Everything here is fully customizable, as we will see in a minute.

Let's write a YAML file to feed this with data:

# conf.taml
enableLogs: true
logDirectory: /var/log/my-logs

Now in order to load them from that file, we use the out-of-the-box YamlFile configuration source:

const source = new ConfEagerSources.YamlFile("conf.yaml", 100); // ignore this 100 for now
const localConfiguration = source.create(LocalConfiguration);

This will look for properties named enableLogs and logDirectory in the given YAML file, if one of them is not found, it will throw an Error. Then it validates their types to be boolean and string, if one of them contains an illegal value it will throw an Error. Lastly, it binds the localConfiguration instance to the source; This means that if the source changes, in this case the YAML file, the in-memory values of localConfiguration properties gets immediately updated.

Finally, in order to read the configuration:

localConfiguration.enableLogs; // => boolean
localConfiguration.logDirectory; // => string

Schema Customization

As mentioned above, ConfEagerJS uses Smoke Screen to define and validate configuration schemas. Smoke Screen uses @exposed decorator to mark and define properties. Property exposure may be customized with the following:

  • as?: string - By default, properties are looked up in the source by their property name. This may be overriden by specifying an explicit key.
  • type?: PropertyType - By default, property types are not validated. A property type may be used to validate the type and value at runtime. Property types are available under Smoke Screen PropertyTypes namespace, or may be explicitly implemented. (Further reading in PropertyType the JSDocs)
  • validator?: (value: any) => any - A further validation function to perform a more specific validation and translation if needed.
  • defaultValue?: any - By default, properties are required. A default value may be used to declare the property optional. Then, in case the property is missing, it receives the given default value.
  • nullable?: boolean - By default, properties may not receive null values. This may by changed using this field.

Configuration Source

A ConfEager configuration source is denoted by the class ConfEagerSource. The purpose of this class is to connect to a data source and get the data from it, then, if possible, watch for updates and notify when data is updated.

To implement a custom configuration source, one abstract method must be implemented - getData(): { [key: string]: any };, which returns all the data as a JS object. For example, we may implement a MySQL data source, which read data from a certain MySQL table and returns it:

const mysql = require('mysql');

class ConfEagerMySQLSource extends ConfEagerSource {

    private readonly _data: {[key: string]: string};

    constructor(private readonly refreshInterval: number,
                private readonly host: string,
                private readonly user: string,
                private readonly password: string,
                private readonly database: string) {
        super();
        this._data = {};
    }

    load() {
        setInterval(() => this.load, this.refreshInterval);
        const __this = this;
        return new Promise<void>((resolve, reject) => {
            const connection = mysql.createConnection({
                host: __this.host,
                user: __this.user,
                password: __this.password,
                database: __this.database
            });
            connection.connect();
            connection.query('SELECT * FROM config', (error: any, results: {[key: string]: string}[]) => {
                if (error) {
                    reject(error);
                }
                else {
                    for (const result of results) {
                        __this._data[result["key"]] = result["value"];
                    }
                    __this.notifyUpdate();
                    resolve();
                }
            });
        });
    }

    protected protected abstract getData(): { [key: string]: any } {
        return this._data;
    }

}

// then we can use it like that:
const source = new ConfEagerMySQLSource(30000, "localhost", "username", "password", "db");
source.load().then(() => {
    const conf = source.create(ConfigurationClass)
});

The same way, we may use any other source, such as Consul KV, ZooKeeper, Redis or any other local or remote source. Further more, this is just an example implementation of a MySQL source. In order to be simple, lightweight and customizable, ConfEagerJS is not shipped with an implementation of those data sources, but rather, aims to provide a simple interface for implementing those fixes for your project needs. Also note that the database information is needed to connect; This may be taken from some local configuration source, e.g. environment variables, local configuration file, etc. We just need to define another configuration class with some local configuration source, initialize it, and then use the resulted configuration to connect to the remote configuration source here. This actually is considered a good practice, in which we separate our local configuration (i.e. paths on current machine, machine identity, etc...) from cluster configuration (i.e. business logic parameters, or other configuration that relates to other nodes in the cluster). Lastly, note that this implementation support updates using interval. A better implementation would involve some kind of mechanism to subscribe for updates. When we want to trigger an update for all bound configuration instances we may call the inherited notifyUpdate() method as shown in the example. A pub/sub implementation would look something like that:

class ConfEagerCustomSource extends ConfEagerSource {

    private readonly _data: {[key: string]: string};

    private readonly _client: Client;

    constructor() {
        this._client = ...
        this._data = client.getData();
        const __this = this;
        this._client.registerForUpdates(() => {
            __this._data = __this._client.getData();
            __this.notifyUpdate();
        });
    }

    protected protected abstract getData(): { [key: string]: any } {
        return this._data;
    }

}

Note how we must first update the data, and then call the notifyUpdate() method.

Binding

Once we've initialized all the data in the source, we may use the source to create and bind a configuration instance. A configuration instance is an instance of any class which specifies properties via Smoke Screen @exposed decorator. Binding an instance to a source, means that the instance properties are loaded with the source data, and that once the source data is updated, the instance fields are seamlessly updated immediately. A configuration source provides two methods for that end, create<T>(configurationClass: Constructable<T>) which takes a configuration class and returns a new instance which is bound to the source, and bind(instance: any), which take an already instantiated configuration object, and just binds it to the source; This may be used when when an instance should be manually instantiated (for example when we want to pass parameters to the constructor).

Out-of-the-Box Configuration Sources

ConfEagerJS provides the following out-of-the-box configuration sources:

  • ConfEagerSources.JsonFile which read data from json files and supports file watch to track file updates.
  • ConfEagerSources.YamlFile which read data from yaml files and supports file watch to track file updates.
  • ConfEagerSources.PropertiesFile which read data from properties files and supports file watch to track file updates.
  • ConfEagerSources.EnvironmentVariables which read data from the Environment Variables.
  • ConfEagerSources.Combinator which receive other sources, and chain them one after the other for each property, until it is found in either of them.
  • ConfEagerSources.StubSource which receive an object containing data and use it as it's source.