Tutorial - Configuration

What you’ll learn

  1. How Granitic defines configuration
  2. How to define different configuration files for different environments
  3. How JSON configuration files are merged together

Prerequisites

  1. Follow the Granitic installation instructions
  2. Read the before you start tutorial
  3. Either have completed tutorial 1 or open a terminal and run
cd $GOPATH/src/github.com/graniticio
git clone https://github.com/graniticio/granitic-examples.git
cd $GOPATH/src/github.com/graniticio/granitic-examples/tutorial
./prepare-tutorial.sh 2

Related GoDoc

https://godoc.org/github.com/graniticio/granitic/config

Configuration

Granitic applications use JSON files to store configuration which is loaded when the application starts. Any valid JSON file is a valid configuration file:

{
    "exampleBool": true,
    "exampleNumber": 1.0,
    "anotherNumber": 25,
    "exampleString": "value",
    "stringArray": ["a","b", "c"],
    "numberArray": [1,2,3],
    "exampleObject": {
        "anotherString": "anotherValue"
    }
}

Granitic uses the term configuration path to express the fully-qualified name of a variable in configuration. In the above example exampleBool is a configuration path and so is exampleObject.anotherString

Configuring a Granitic application

When a Granitic application starts, it looks for configuration files before doing anything else. The location of these files is specified using the (-c) command line parameter. The default value for -c is resource/config, so in previous examples running:

recordstore

is the equivalent of running:

recordstore -c resource/config

The value of the -c parameter is expected to be a comma separated list of:

  • Relative or absolute paths to JSON files
  • Relative or absolute paths to directories
  • Absolute URIs of HTTP or HTTPS resources

Support for directories and remote URIs will be discussed in a later tutorial. For now, open a terminal and run:

cd $GOPATH/src/granitic-tutorial/recordstore
grnc-bind
go build
./recordstore -c resource/config/config.json

This runs your recordstore application, specifically stating that a single configuration file resource/config/config.json should be used. Stop the application with CTRL+C

Injecting configuration into your components

The Go IoC container automatically injects configuration values into your components. Modify the file endpoint/artist.go so it looks like:

package endpoint

import (
  "github.com/graniticio/granitic/ws"
  "context"
)

type ArtistLogic struct {
  EnvLabel string
}

func (al *ArtistLogic) Process(ctx context.Context, req *ws.WsRequest, res *ws.WsResponse) {

  a := new(ArtistDetail)
  a.Name = "Hello, World from " + al.EnvLabel

  res.Body = a
}

type ArtistDetail struct {
  Name string
}

We’ve added a new field to the struct, EnvLabel and have changed our Hello, World! message to include the value of that field. As the name suggests, this will vary depending on which environment our code is running in.

Now modify the artistLogic definition in your resource/components/components.json file so it looks like:

"artistLogic": {
    "type": "endpoint.ArtistLogic",
    "EnvLabel": "conf:environment.label"
}

This is a configuration promise - you are telling Granitic to find a value in configuration with the configuration path environment.label and inject it into the artistLogic component’s EnvLabel field at runtime.

If you now run:

grnc-bind && go build && ./recordstore -c resource/config/config.json

you will see an error message similar to:

17/Jan/2017:16:39:42 Z FATAL [grncContainer] No value found at environment.label

Granitic adopts a fail-fast model for configuration and will not allow an application to start if it relies on configuration that is undefined. Rather than just adding the expected configuration to resource/config/config.json, we’ll use this opportunity to show how configuration files can be used to make deploying your application in multiple locations more straightfoward.

Multiple configuration files

Only the simplest applications will use a single configuration file. Complex applications will split their configuration into multiple files to improve readability and maintainability, but most applications will want to separate the configuration that is common to each deployment of an application from that configuration that changes across different deployment environments and from one instance of an application to another.

The rest of this tutorial simulates deploying an instance of a web-service across multiple environments and then multiple instances running on a single server.

Create two new files:

resource/env/production.json

{
  "environment": {
    "label": "PROD"
  }
}

resource/env/development.json

{
  "environment": {
    "label": "DEV"
  }
}

As you’ve only changed configuration files, you don’t need to rebuild. You can just run:

./recordstore -c resource/config,resource/env/production.json

or

./recordstore -c resource/config,resource/env/development.json

And visit http://localhost:8080/artist to see a different result depending on which config file you’re using.

Tidying up

In later tutorials, we’ll want the option of running recordstore without specifiying a list of config files, so change your

resource/config/config.json
file so that it looks like:

{
    "Facilities": {
        "HttpServer": true,
        "JsonWs": true
    },
    "environment": {
        "label": "UNSET"
    }
}

Overriding configuration

To maximise use of resources, you may want to run multiple instances of a web service on a single host. This means each instance must have a different HTTP port assigned to it.

This is an example of how you can override previously defined configuration items with new values.

Create two new files:

resource/instance/instance-1.json

{
  "HttpServer": {
    "Port": 8081
  }
}

resource/instance/instance-2.json

{
  "HttpServer": {
    "Port": 8082
  }
}

You can now run:

./recordstore -c resource/config,resource/env/development.json,resource/instance/instance-1.json

and in a separate terminal run:

cd $GOPATH/src/granitic-tutorial/recordstore
./recordstore -c resource/config,resource/env/development.json,resource/instance/instance-2.json

And you now have two separate instances of your recordstore application running and listening on different ports.

Configuration merging

In previous examples, you will have noticed that the default HTTP port for Granitic applications is 8080. This is not hard-coded, it is defined in another configuration file that is included with Granitic itself called a facility configuration file. Making sure your applications can read these built-in configuration files is why you need to set the GRANITIC_HOME environment variable to point to an installation of Granitic.

During startup, Granitic builds up a single view of configuration by merging together all of your configuration files with all of the built-in configuration files that can be found under $GRANITIC_HOME/resource/facility-config.

You can see the order in which Granitic is merging configuration files together by starting an application with the -l TRACE parameter, which sets Granitic’s initial log-level to TRACE. Your application’s configuration files take precedence over the built-in facility configuration files, so in this example the value of HttpServer.Port in facility-config/httpserver.json is replaced with the value in your recordstore-1.json or recordstore-2.json file.

Merging rules

The rules by which configuration two files are merged together are specified in the Granitic GoDoc, but the following example illustrates the key rules (note the configuration items are an illustration and do not relate to any specific Granitic features)

a.json

{
    "server": {
        "name": "localhost",
        "network": {
            "interfaces": ["192.168.0.2","127.0.0.1"],
            "sslOnly": false,
            "seed": 1.98311
        },
        "security":{
            "mode": 0
        }
    }
}

b.json

{
    "server": {
        "name": "testserver",
        "network": {
            "interfaces": ["10.123.0.5"],
            "certPath": "/tmp/cert.key",
            "sslOnly": true
        },
        "metrics":{
            "enabled": true
        }
    }
}

merged together becomes:

{
    "server": {
        "name": "testserver",
        "network": {
            "interfaces": ["10.123.0.5"],
            "certPath": "/tmp/cert.key",
            "sslOnly" true
        },
        "security":{
             "mode": 0
        },
        "metrics":{
            "enabled": true
        }
    }
}

Files are merged from left to right, the final value of a configuration item present in more than one file is the one defined in the rightmost file. The behaviour that might be most unexpected is how arrays are handled - the contents of arrays are not merged together, but replaced.

Recap

  • Granitic applications store configuration in JSON files which are loaded when an application starts.
  • Use multiple configuration files to organise your application and to support multiple environments and deployments.
  • Configuration is injected into your Go objects by using config promises in your application’s component definition file. you change your component definitions.
  • All of your configuration files are merged together with Granitic’s built-in facility configuration files to provide a single view of configuration.

Next

The next tutorial covers application logging