All Projects → AvisoNovate → Config

AvisoNovate / Config

Licence: apache-2.0
Configure a system using EDN files and clojure.spec

Programming Languages

clojure
4091 projects

Labels

Projects that are alternatives of or similar to Config

Stf Vue Select
stf vue select - most flexible and customized select
Stars: ✭ 61 (-15.28%)
Mutual labels:  component
Made With Love
🚀 An experimental project which demonstrates an Angular Package which contains Angular Elements and Schematics
Stars: ✭ 67 (-6.94%)
Mutual labels:  component
Utils
🛠 Lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.
Stars: ✭ 1,158 (+1508.33%)
Mutual labels:  component
React Pin Field
📟 React component for entering PIN codes.
Stars: ✭ 63 (-12.5%)
Mutual labels:  component
Horsey
🐴 Progressive and customizable autocomplete component
Stars: ✭ 1,146 (+1491.67%)
Mutual labels:  component
React Native X Bar
🎩 A flexible, lightweight bar component for common UI patterns in React Native
Stars: ✭ 68 (-5.56%)
Mutual labels:  component
Redemo
react demo component
Stars: ✭ 60 (-16.67%)
Mutual labels:  component
Rn Collapsing Tab Bar
Collapsing header with tabs for react native
Stars: ✭ 71 (-1.39%)
Mutual labels:  component
Vue Clock2
vue clock component 😀
Stars: ✭ 67 (-6.94%)
Mutual labels:  component
Security Guard
The Guard component brings many layers of authentication together, making it much easier to create complex authentication systems where you have total control.
Stars: ✭ 1,157 (+1506.94%)
Mutual labels:  component
Class Loader
[DEPRECATED] The ClassLoader component provides tools to autoload your classes and cache their locations for performance.
Stars: ✭ 1,131 (+1470.83%)
Mutual labels:  component
Daggraph
Dagger dependency graph generator for Android Developers
Stars: ✭ 1,140 (+1483.33%)
Mutual labels:  component
Vue Autonumeric
A Vue.js component that wraps the awesome autoNumeric input formatter library
Stars: ✭ 68 (-5.56%)
Mutual labels:  component
Selectivity
Modular and light-weight selection library
Stars: ✭ 1,113 (+1445.83%)
Mutual labels:  component
French Press Editor
☕ An offline-first rich text editor component.
Stars: ✭ 69 (-4.17%)
Mutual labels:  component
Dzhtmltext
Delphi and Lazarus HTML Label component
Stars: ✭ 60 (-16.67%)
Mutual labels:  component
Security Http
Security provides an infrastructure for sophisticated authorization systems, which makes it possible to easily separate the actual authorization logic from so called user providers that hold the users credentials. It is inspired by the Java Spring framework.
Stars: ✭ 1,146 (+1491.67%)
Mutual labels:  component
React Json Graph
React component for rendering graphs
Stars: ✭ 71 (-1.39%)
Mutual labels:  component
React Frame Component
Render your React app to an iFrame
Stars: ✭ 1,163 (+1515.28%)
Mutual labels:  component
Prelodr
A simple Material preloader inspired by Google Inbox.
Stars: ✭ 68 (-5.56%)
Mutual labels:  component

= Config - Smart and flexible system configuration

image:http://clojars.org/io.aviso/config/latest-version.svg[Clojars Project, link="http://clojars.org/io.aviso/config"]

Config is a very small library used to handle configuration of a server; it works quite well with a system defined in terms of link:https://github.com/stuartsierra/component[Stuart Sierra's component library].

link:https://medium.com/@hlship/microservices-configuration-and-clojure-4f6807ef9bea[This posting] provides a lot of detail on the requirements and capabilities of config.

link:http://avisonovate.github.io/docs/config/[API Documentation]

== Overview

Config reads a series of files, primarily from the classpath. The files contain contain configuration data in link:https://github.com/edn-format/edn[EDN] format.

The files are read in a specific order, based on a set of profiles. The name of the file to read is based on the profile and the variant (described shortly).

The contents of all the configuration files are converted to Clojure maps and are deep-merged together.

The intent of profiles is that there is an approximate mapping between components and profiles: generally, each component will have exactly one profile.

Each component may, optionally, define a link:http://clojure.org/guides/spec[spec] for its configuration data.

But what about the link:http://12factor.net/config[12 Factor App]'s guideline to store configuration only as environment variables? This is embraced by config, because the files may contain environment variable references that are expanded at runtime.

At link:http://www.aviso.io/[Aviso], we use these features in a number of ways. For example, for quick testing we combine a number of microservices (each of which has its own configuration profile and schema) together into a single system. Meanwhile, in production (on AWS) we can build a smaller system with a single microservice. We can also provide an additional configuration file that enables configuration overrides based on environment variables set by CloudFormation.

== Implementing Components

You might define a web service as:

[source,clojure]

(require '[clojure.spec :as s] '[io.aviso.config :as config] '[com.example.jetty :as jetty] '[com.stuartsierra.component :as component])

(defrecord WebService [port request-handler jetty-instance]

config/Configurable

(configure [this configuration] (merge this configuration))

component/Lifecycle

(start [this] (assoc :jetty-instance (jetty/run-jetty request-handler {:port port :join? false})))

(stop [this] (.stop jetty-instance) (assoc this :jetty-instance nil))

(s/def ::port (s/and int? pos?) (s/def ::config (s/keys :req-un [::port])

(defn web-service [] (-> (map->WebService {}) (component/using [:request-handler]) (config/with-config-spec :web-service ::config)))

This is a standard component, with the start and stop lifecycle methods, a dependency on another component (:request-handler), and a local field for the instance of Jetty managed by the component.

In addition, the component is configurable: it implements the Configurable protocol, and receives its specific configuration as a map. The configuration passed to the component conforms to the ::config spec; it will have a :port key.

The configure method is invoked before the start method.

== Providing Configuration Files

Without configuration files, your application will not start up; you will see errors about invalid specs, because there is (in this example) no :web-service top-level key, and no :port key below that.

Configuration files are located on the class path, within a conf package; this means inside the resources/conf folder in a typical project.

For the :web-service component, you would provide a default configuration file, web-service.edn:

[source,clojure]

{:web-service {:port 8080}}

== Starting the System

And finally, build and start a system from all this:

[source,clojure]

(let [system (component/system-map :web-service (web-service) :request-handler (request-handler))] (-> system (config/configure-using nil) component/start-system))

The configure-using function reads the configuration files and assembles the configuration map, then applies the the configuration to each component.

The second parameter to configure-using is map of options.

configure-using generates default profiles from components in the system. Any component that declared a configuration key using with-config-spec will be included.

Here, the default list of profiles is just :web-service.

The :web-service keyword is being used in three ways here:

  • As the component key in the system map
  • As the name of the profile for the component, identifying the configuration file(s) for the component
  • As the key within the system configuration containing the component's specific configuration

Unless you have a compelling reason otherwise, you should always follow this pattern; the profile name should match the configuration key, which should match the component key in the system map.

The default profiles are in dependency order. If :request-handler has a configuration key, then it will be ordered ahead of :web-service, because the :web-service component depends on the :request-handler component.

For each component that defines a configuration spec, configure-using will:

  • Extract the component's configuration
  • Conform the configuration
  • Throw an exception if the configuration contains invalid data
  • Either invoke the configure method, or associate a :configuration key, providing the conformed configuration

== Configuration Overrides

But what if you want to override part of the :web-service configuration ... for example, to specify a different port? This is very common ... your local development configuration is going to vary considerably from your deployed production configuration.

This can be accomplished in a number of ways.

=== Explicit Overrides

First off all, it is possible to provide an explicit map of overrides when constructing the configuration map:

[source,clojure]

(config/configure-using {:overrides {:web-service {:port 9999}}})

However, that option is generally intended for special cases, such as overrides during testing.

Most other approaches involve controlling which files are loaded to form the system configuration.

=== Explicit Profiles

So if you wish to have some overrides, you could provide a configuration file named overrides.edn and ensure that is loaded after the :web-service profile:

[source,clojure]

(config/configure-using {:profiles [:overrides]})

Implicit profiles, via with-config-spec are loaded first, then explicit profiles in the options. Order can be important here, and later-loaded profiles will override earlier profiles if there are conflicts.

=== Variants

Another option is to support an additional variant to customize the configuration.

For each profile, config searches for any variant.

In this case, the file name would be web-service-production.edn. web-service comes from the profile and production from the variant.

[source,clojure]

(config/configure-using {:variants [:production]})

The nil variant (web-service.edn) is always loaded first to provide the defaults, the provided variants (when they exist) overlay the nil variant.

In this example, the normal configuration is safe; it's for local testing. Only when deploying to production does the :production variant get added in.

=== Additional Files

You could also explicitly load one or more configuration files stored on the file system (rather than as classpath resources):

[source,clojure]

(config/configure-using {:additional-files ["overrides/production.edn"]})

This is another possible way to provide overrides that only apply in production; the difference being that this file is on the file system, not packaged inside the application JARs.

== Runtime Properties

Often, especially in production, you don't know all of the configuration until your application is actually started. For example, in a cloud provider, important IP addresses and port numbers are often assigned dynamically. This information is provided to the processes via environment variables.

Although this information could be extracted by startup code, and provided to the configure-using function using the :overrides configuration, that is both rigid and clumsy.

Instead, it is possible to reference these dynamic properties inside the configuration files using the special reader macros supplied by config.

Properties are:

  • Shell environment variables.

  • JVM System properties.

  • The :properties option, passed to configure-using.

The following reader macros are available:

#config/prop:: Accesses dynamic properties. The value is either a single string key, or a vector of string key followed by a default value.

#config/join:: Joins a number of values together to form a single string; this is used when an building a single string from a mix of properties and static text.

#config/long:: Converts a string to a long value. Typically used with #config/prop.

#config/keyword:: Converts a string to a keyword value. Typically used with #config/prop.

Here's an example showing all the variants:

[source,clojure]

{:connection-pool {:user-name #config/prop ["DB_USER" "accountsuser"] :user-pw #config/prop "DB_PW" :url #config/join ["jdbc:postgresql://" #config/prop "DB_HOST" ":" #config/prop "DB_PORT" "/accounts"]} :web-server {:port #config/long #config/prop "WEB_PORT"}}

In this example, the DB_USER, DB_PW, DB_HOST, and DB_PORT, and WEB_PORT environment variables all play a role (though DB_USER is optional, since it has a default value).

In the final configuration, the key [:connection-pool :url] is a single string, and the key [:web-server :port] is a long (not a string).

== License

Config is available under the terms of the Apache Software License 2.0.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].