Configuration merging

Back to: Reference | Configuration


A key principle in Granitic’s configuration model is that of configuration layers, where an application has a base configuration then additional configuration is layered on for specific environments and deployments of your application.

The is no strong concept of layer in Granitic, instead a layer is one or more configuration files that logically represent a layer. In a simple application, each layer will probably be represented by a single JSON file, but more complex applications may choose to have multiple files per each layer.

At runtime, Granitic resolves all of the configuration files and URLs that are available to your application and merges them together to form a single unified view of application confgiuration.

Example

This process is explained in detail, but requires an example, so imagine an application that is started with the following command:

exampleapp -c base.json,production,instance.json

And the following configuration files available:

base.json

{
  "instance": "dev-example",
  "ApplicationLogger":{
    "GlobalLogLevel": "DEBUG"
  }
}

production/config.json

{
  "instance": "prod-example"
}

production/logging.json

{
  "ApplicationLogger":{
    "GlobalLogLevel": "ERROR"
  }
}

instance.json

{
  "instance": "example-1/8080"
}

Merging order

The order in which configuration files are presented to Granitic is significant. In the event that more than one configuration file specifies a value for a given configuration path, the final merged configuration will contain the value in the rightmost file in which the configuration path is defined.

So in the above example, the order in which Granitic will process configuration files is:

  • base.json
  • production/config.json
  • production/logging.json
  • instance.json

After merging is complete, the value of the configuration path:

  • instance will be example-1/8080 (last defined in instance.json)
  • ApplicationLogger.GlobalLogLevel will be ERROR (last defined inproduction/logging.json)

Merging rules for JSON types

If a configuration path appears in two or more files, the eventual value of that field is determined by the types involved.

Strings, numbers and booleans

When merging configuration paths that have a boolean, string or number value, the eventual value is simply the value in the rightmost file.

For example:

exampleapp -c a.json,b.json

a.json

{
  "a": 1,
  "b": false,
  "c": "apple",
  "d": -10
}

b.json

{
  "a": 2,
  "b": true,
  "c": "orange" 
}

results in an effective configuration of:

{
  "a": 2,
  "b": true,
  "c": "orange",
  "d": -10 
}

Objects (maps)

When merging configuration paths that have a JSON object (map) value, the result is a map containing all referenced keys. When a key appears in more than one definition of the map, the eventual value is that from the rightmost file.

For example:

exampleapp -c a.json,b.json

a.json

{
  "someObject": {
    "a": 1,
    "b": false,
    "c": "apple",
    "d": -10  
  }
}

b.json

{
  "someObject": {
    "a": 2,
    "b": true,
    "c": "orange" 
  }
}

results in an effective configuration of:

{
  "someObject": {
    "a": 2,
    "b": true,
    "c": "orange",
    "d": -10,
  } 
}

Arrays

When merging configuration paths that have an array of any type, the eventual value is the value in the rightmost file. The contents of arrays are not merged as there is no sensible and consistent strategy for doing so.

For example:

exampleapp -c a.json,b.json

a.json

{
  "a": [1,2,3],
  "b": ["a", "b", "c"]
}

b.json

{
  "a": [4,5,6]
}

results in an effective configuration of:

{
  "a": [1,2,3],
  "b": ["a", "b", "c"]
}

Next: Logging

Prev: Configuration type handling