Skip to content
Dmitry Litvintsev edited this page Jul 2, 2020 · 12 revisions

Configuration principles

A program's configuration system must be able to present external information to the application to control its run-time behavior. Such information often comes in the form of command-line options, configuration files, environment variables, STDIN, etc.

This page discusses how this information should be formed and provided to the application.

Preparation vs. presentation

A well-designed configuration system separates the preparation of configuration information from the presentation of that information to the program. Such a design expresses a clear separation of computing responsibilities, and it allows for efficient introspection and debugging of configuration artifacts.

Data declaration vs. data calculation

A program's configuration is often better expressed as a data declaration data than a data calculation. This is because a data declaration is typically:

  • easier to understand,
  • easier to reproduce behavior,
  • easier to store, and
  • easier to debug.

In the table below, the value of par1 is declared in the first column whereas it is calculated in the second column by a function.

Data declaration Data calculation
par1 = val1 par1 = calc_val1()

Although calc_val1() may conveniently prepare the value to be assigned to par1, the function call inserts a level of indirection between the actual value and the parameter assignment. Such indirection introduces:

  • the potential for programming errors,
  • possible dependencies on utilities (as required by calc_val1()) that may change over time, and
  • harder to understand configurations.

The decisionengine project thus favors data-declaration techniques over data-calculation ones.

Jsonnet language

Powerful programming languages have facilities developers rely upon to ensure minimal points of maintenance. In a configuration context, however, those facilities are the very things we argue should not be used (for reasons described above).

It is still possible to configure a program with data declaration while still ensuring single points of maintenance. The Jsonnet language, chosen as decisionengine's configuration language, provides such an approach as long as the following guidelines are heeded.

Jsonnet usage guidelines

This section describes some best practices in forming decisionengine configurations. It is expected that these guidelines will change over time based on experience using Jsonnet.

Use local variables that can be used elsewhere

local my_var = 42;
{
  par1: my_var
  par2: my_var 
}

Equivalent configuration

{
  par1: 42
  par2: 42
}

Import .libsonnet files as local variables

local vars = import 'other.libsonnet';
{
  par: vars.nested_var
}

Use object composition to provide defaults

Jsonnet allows the ability to provide default values that can be overridden. For example:

local defaults = {
  font: "Arial",
  color: "black"
};
{
  font_style: defaults {
    color: "red",
    style: "italics"
  }
}

Equivalent configuration

{
  font_style: {
    font: "Arial",
    color: "red",
    style: "italics"
  }
}

Please consult the Jsonnet documentation to learn more about object composition, especially in for cases where the + and +: syntax is necessary.

Use simple arithmetic at the top-level

file_size: 200 * 1024 * 1024 # 200 MiB

Equivalent configuration

{
  file_size: 209715200 # 200 MiB
}

Things to avoid

As discussed above, programming facilities that introduce significant computation should be avoided in a configuration environment. Use of the following Jsonnet facilities is thus discouraged:

  • functions
  • array slices
  • conditional expressions
  • computed field names
  • array and object comprehensions

Migration plan

1. Create reference files for current configuration files

  • Identify all Python-based configurations that can be internally converted to JSON via the Python json module
    • Dump the JSON representation to a file, which will serve as the reference file for that configuration file.
  • For any Python-based configuration that cannot be internally converted to JSON:
    • Identify the components of the configuration that cannot be converted to JSON, and make them compliant.
    • Update the affected Python modules to use the JSON-compliant configuration.
    • Dump the JSON-compliant configuration to a file, which will serve as the reference file for that configuration.

2. Change all current configuration files to be JSON-compliant

With this step, no internal conversion to JSON is allowed. The goal here is to actually migrate from the Python-based configuration to the Jsonnet/JSON based one.

  • To perform this migration, a script should read the configuration file and try to evaluate it as a JSON document.
  • If the evaluation succeeds, then the document should be renamed with a .jsonnet extension
  • If the evaluation fails, then the document must be adjusted until it is JSON compliant.
    • This might be achieved by creating a migration script that can do simple text replacement (e.g. True -> true)
    • Once evaluation as JSON succeeds, the file should be renamed with a .jsonnet extension.

3. Compare to reference files

  • Each .jsonnet file should be read in and dumped to a JSON test file.
  • The contents of the test file and corresponding reference file must be identical.