Skip to content

manuelp/confunion

Repository files navigation

confunion

Confunion is a library to manage configuration files.

Features

  • Clojure and Java APIs.
  • Merging of multiple configuration files.
  • Supports schemas, for documentation and validation.
  • Schemas can be composed from multiple, overlapping fragments.
  • Configurations and schemas are written in EDN format.
  • Informative error messages.
  • Import/overwrite configuration in java.util.Properties objects (for interoperability purposes).

Changelog

See the changelog file.

Motivation

I needed a way to easily manage environment-specific configuration files for an application that is installed in multiple heterogeneous environments: development machines, QA servers, pre-production and production installations for several customers, etc. In short, I need a way to load and validate against a schema an EDN map by merging the content of a base file with an (optional) override one, both read from a set of possible paths in which to find them. The schema is particularly important in my case for documentation an validation purposes (so that I can keep the application and various environment-specific configurations in sync).

So, my main requirements were:

  • It has to be file-based. Preferably in EDN format, so I can have a semantically rich but also general configuration format.
  • A configuration should be built by merging multiple EDN maps read from a set of possible locations (configurable).
  • It should validate the resulting configuration against a schema that describes the properties it should have.
  • It should not be stateful, how this map is used should not be a concern for this library.

I have not found an existing library that satisfies all of my requirements. For example:

  • Environ it's based on environment variables and system properties.
  • Nomad is based around the concept of hostname-based configuration, which doesn't fit so well with the use cases I had in mind.
  • clonfig is environment variables only.
  • conf-er is based on a single file.

Plus, no existing library (that I'm aware of) supports schemas, a requirement that for me is key.

My quick analysis is probably incomplete, so if you think your library fits my use case then let me know and let's join forces!

Project Maturity

This project has been used in production for three years, so it can be considered production-ready.

Supported Clojure/Java versions

Confunion has been tested with Clojure 1.6 and JDK 1.7, however it should also work with Clojure 1.5 and JDK 1.6.

installation

Artifacts are released to Clojars.

Leiningen

Add dependency in your project.clj:

Current Version

Maven

Add Clojars repository definition to your pom.xml:

<repository>
  <id>clojars.org</id>
  <url>http://clojars.org/repo</url>
</repository>

and then the dependency:

<dependency>
  <groupId>me.manuelp</groupId>
  <artifactId>confunion</artifactId>
  <version><!-- x.y.z --></version>
</dependency>

Usage

The surface of this library is quite small, basically there are only two entry points:

  • Configuration loading and validation.
  • java.util.Properties writing.

Schema Format

A schema is defined by an EDN data structure (see here for a comparison between EDN and JSON): a vector of maps, each one of them is a description of a property that a configuration map should (or may) have. Every parameter description should have three properties:

  • :schema/param: the keyword of the parameter (it's name, or code if you want).
  • :schema/doc: a documentation string (which is useful both for documentation and useful error messages).
  • :schema/mandatory: a boolean that indicates if the described parameter is mandatory or not.
  • :schema/type: type of the valid parameter values. One of:
    • :schema.type/string
    • :schema.type/boolean
    • :schema.type/number
    • :schema.type/any (this is a catch-all, to use only if none of the existing keys describe the value type)

All this parameter description entries are mandatory.

The schema currently is only used to verify if all entries are documented, all mandatory ones are present, and their values are of the right type.

A simple example:

[{:schema/param :a
  :schema/doc "A very useful configuration"
  :schema/mandatory false
  :schema/type :schema.type/string}
 {:schema/param :b
  :schema/doc "Some other useful configuration parameter."
  :schema/mandatory true
  :schema/type :schema.type/boolean}]

Multiple schema fragments can be composed in a single, global schema. If a parameter is specified multiple times in different fragments, it'll be used the last definition.

Configuration Map Format

It's a standard EDN map. An example:

{
  :a 1
  :b [2 "hello" "Sarah"]
  :c {
       :key "value"
       :another [21 71]
     }
  :d #{"A" "B"}
}

Since right now schemas can validate only simple values (scalars or at the very least homogeneous collections), it's usually better to structure a configuration with those type of values.

Clojure

The higher-level function is:

(require '[confunion.core :as conf])

(conf/load-configuration "path/to/schema.edn"
                         ["paths/to/possible.edn" "base/configuration/files.edn"]
                         ["overrides/files.edn" "/paths/to/inspect.edn"])
;; Returns the merged and validated map, or an exception with a detailed
;; message.

This is good if you want to merge the content of a base file and an optional overrides one, both to be searched in an ordered sequence of paths.

You can also compose a schema from (at most) a couple of fragments:

(require '[confunion.core :as conf])

(conf/compose-configuration ["path/to/schema.edn"]
                            ["path/to/additional-schema.edn" "another/schema-fragment.edn"]
                            ["paths/to/possible.edn" "base/configuration/files.edn"]
                            ["overrides/files.edn" "/paths/to/inspect.edn"])
;; Returns the merged and validated map, or an exception with a detailed
;; message.

If you want to write a generic EDN map into a java.util.Properties object, you can use:

(require '[confunion.properties :as props])

(props/add-properties (new java.util.Properties)
                      configuration-map)

Java

The Java API is a mirror of the Clojure one (only much more verbose). You only need instantiate and use a Confunion object:

Confunion cu = new Confunion();
Map configurationMap = cu.loadConfiguration("path/to/schema.edn",
                                  new ArrayList<String>() {
                                    {
                                      add("paths/to/possible.edn");
                                      add("base/configuration/files.edn");
                                    }
                                  },
                                  new ArrayList<String>() {
                                    {
                                      add("overrides/files.edn");
                                      add("/paths/to/inspect.edn");
                                    }
                                  });

cu.addProperties(new Properties(), configurationMap);

or, for composing the schema too:

Confunion cu = new Confunion();
Map configurationMap = cu.composeConfiguration(
                                  Collections.<String>singletonList("path/to/schema.edn"),
                                  new ArrayList<String>() {
                                    {
                                      add("paths/to/additional-schema.edn");
                                      add("another/schema-fragment.edn");
                                    }
                                  }
                                  new ArrayList<String>() {
                                    {
                                      add("paths/to/possible.edn");
                                      add("base/configuration/files.edn");
                                    }
                                  },
                                  new ArrayList<String>() {
                                    {
                                      add("overrides/files.edn");
                                      add("/paths/to/inspect.edn");
                                    }
                                  });

cu.addProperties(new Properties(), configurationMap);

There is also a lower-level API accessible through the ConfunionEngine class.

Documentation

You can generate a literate-like export of this project sources by using Marginalia. Basically (assuming you already have Leiningen installed) you just need to run this command:

lein marg

And open the resulting docs/uberdoc.html file with a web browser.

License

Copyright © 2014-now Manuel Paccagnella

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

About

Library for managing configuration based on EDN files.

Resources

License

Stars

Watchers

Forks

Packages

No packages published