Kitura

As Server-Side Swift application development gains popularity, there is growing demand for a simpler way to manage application configuration data. Depending on the environment or platform, an application may load configuration data from a file on system, from environment variables, or even from a remote source over HTTP. The way the data are structured can also differ; an application’s running port may be designated by an APPLICATION_PORT environment variable in a container, but the same value may be designated by a PORT setting on a cloud platform. Also, settings such as load balance values may be injected at deployment time, and as a result the application need to combine configuration data from multiple sources to get all the values it needs. Addressing these scenarios can impede productivity as developers need to spend more time writing code to manage configurations, leaving less time for writing the actual business logic.

To reduce the burden of managing Swift application configurations, we are introducing a new Configuration framework. This framework is composed of two new packages, Configuration and CloudConfiguration, that can drastically simplify your application’s configuration management. In addition, these new packages allow developers to embrace the best practices of removing configuration dependencies from their application logic.

Configuration

The core of the framework is the Configuration package, which is inspired by nconf, a popular NodeJS configuration manager. The Configuration package offers convenient APIs to manage configurations. For instance, an application can directly use Configuration to load configurations from files, and then access the loaded configurations by their keys. Here is a short example:

Create a defaults.json

{
    "env": "development",
    "port": 8080,
    "foo": {
        "bar": "baz"
    }
}

and put it alongside the executable. Then, in your application source code, load the file into the configuration store:

import Configuration

let manager = ConfigurationManager().load(file: "defaults.json")

manager["port"]     // port is 8080
manager["foo:bar"]  // foo:bar is "baz"

All of the configuration data in defaults.json are readily available via the ConfigurationManager instance.

You can also overlay configurations from multiple sources; for example, you can load default configurations from a file, and then override those configurations with another file that is injected at deployment stage. If any keys conflict, the one most recently loaded takes precedence. For example:

Create a test.json:

{
    "env": "test",
    "port": 8090
}

and put it alongside the executable and defaults.json. Then, in your application's source code, load both defaults.json and test.json into the configuration store:

import Configuration

let manager = ConfigurationManager().load(file: "defaults.json").load(file: "test.json")

manager["env"]      // env is now "test"
manager["port"]     // port is now 8090
manager["foo:bar"]  // foo:bar is still "baz"

Configuration supports loading configurations from environment variables and command line arguments as well. Here is a quick example showing load from command-line arguments:

import Configuration

let manager = ConfigurationManager().load(file: "defaults.json").load(.commandLineArguments)

// Pass in configurations in command line like so:
// ./swift-app --port=443 --foo.bar=buzz
manager["port"]     // port is now "443"
manager["foo:bar"]  // foo:bar is now "buzz"

You can find examples on loading configurations from environment variables and other sources here.

CloudConfiguration

Building on the configuration management features offered by Configuration, the CloudConfiguration package adds additional APIs for applications to retrieve cloud service configurations. Currently, CloudConfiguration only supports CloudFoundry cloud platforms, such as IBM Cloud. However, we welcome pull requests from the community to add support for other cloud platforms. This will enable applications that use CloudConfiguration to become portable applications that can run on different cloud platforms without any code changes.

CloudConfiguration is implemented as extensions to the ConfigurationManager class, so its usage is largely similar to the usage of the core Configuration package. Here is a short example using CloudConfiguration to retrieve a Cloudant service's configurations in a CloudFoundry environment.

Typically, CloudFoundry injects service configuration data into the environment variables of the application's runtime in a format like this:

{
  "VCAP_SERVICES": {
    "cloudantNoSQLDB": [
      {
        "credentials": {
          "host": "<host>",
          "password": "<password>",
          "port": 443,
          "url": "<url>",
          "username": "<username>"
        },
        "label": "cloudantNoSQLDB",
        "name": "CloudantService",
        "plan": "Lite",
        "provider": null,
        "syslog_drain_url": null,
        "tags": [
          "data_management",
          "ibm_created",
          "ibm_dedicated_public"
        ]
      }
    ]
}

With CloudConfiguration, you can get the Cloudant service's configuration data as an object, without having to traverse the configuration tree manually:

import CloudConfiguration
import Configuration

let manager = ConfigurationManager().load(.environmentVariables)

let cloudantService = try manager.getCloudantService(name: "CloudantService")

// `cloudantService` is an object with `host`, `username`, `password`, `port`, and
// `url` fields populated with values from the VCAP_SERVICES:cloudantNoSQLDB env var
cloudantService.port // port is 443
cloudantService.host // host is "<host>"

// You can still get the configuration values directly, like so:
manager["VCAP_SERVICES:cloudantNoSQLDB:0:name"] // name is "CloudantService"

To see the list of CloudFoundry services that CloudConfiguration currently supports, check its repository here.

The TL;DR

Configuration is a simple but powerful framework for managing Swift application configurations. Using Configuration, you can easily load configuration data from multiple sources and aggregate them in one single store for your application to consume. Additionally, the CloudConfiguration package provides APIs to easily extract CloudFoundry service configuration data, a feature that is very useful for Swift applications deployed to IBM Cloud.

Furthermore, we are constantly looking for ways to improve the Configuration framework, such as by adding support for additional configuration file/data formats as well as support for other cloud platforms. If you have any questions or requests, head on over to the Configuration and CloudConfiguration repositories or our Slack team and let us know.



Star

2 comments on"Managing Swift Application Configuration"

  1. Great, It’s getting better and better.

Leave a Reply