Reading configuration with Aero

20-Jun-2016

Aero is a nice configuration library for clojure. Its README does a good job describing its motivations and design goals. Here's how I'd describe it

Aero provides a library for reading config as EDN with added logic for a small set of tag literals that make sense for configuration.

Reading some aero config is a simple as (read-config source) where the source can be anything that clojure.java.io/reader supports (InputStream, File, URI, String etc). Here's an example

;; config.edn
{:port #or [#env PORT 8080]}

(read-config "config.edn")
;; => {:port 8080}

It's pretty obvious that this bit of EDN, when read, should result in a map where the :port value will take on the value of the PORT environment variable if it exists otherwise it'll be 8080. From this snippet we can see two of the tag literals that aero supports #or and #env.

Profiles

When we configure our applications and tools more often than not we need the configuration to control the behavior for a multiple set of circumstances. The classic example is dev, staging and production environments. Aeros solution is the #profile tag. Here's an example

;; config.edn
{:port #profile {:prod 9000 :stage 8000 :default 8080}}

(read-config "config.edn")                   ;; => {:port 8080}
(read-config "config.edn" {:profile :qa})    ;; => {:port 8080}
(read-config "config.edn" {:profile :prod})  ;; => {:port 9000}
(read-config "config.edn" {:profile :stage}) ;; => {:port 8000}

Note: this isn't just useful for server config we can use it for our tools as well. Have you ever configured the clojurescript compiler with multiple builds that are essentially the same? why not use Aero?

Including more config

One of the more interesting tag literals is #include which essentially takes the tagged value and passes it back to read-config as the source for further reading (as EDN). Nice! This means we could split up config into multiple files. One case where this is helpful is secret storage. We shouldn't keep our secrets (API keys etc.) in source control alongside other config. Instead we can include them from another location.

{:secrets #include ".secrets.edn"}

But one question arises pretty quickly. Include ".secrets.edn" relative to what? The project directory? The file it was included from? The classpath? (for jars). Remember read-config requires the source to be compatible with clojure.java.io/reader. What if we wanted to include files through some other mechanism? Or what if we wanted to read in some other format like json?

Aero use the concept of a resolver which is just a function responsible for transforming the #include value into a valid source for read-config. The default resolver will attempt to resolve paths as files relative to the source it was included from. You can specify your own include resolver in the options passed to read-config.

Conclusion

I like Aero because it makes configuration obvious. It's just EDN with some extra leverage for common configuration tasks (env variables, profiles, etc.). For more on Aero check out the README which covers all the tag literals, extensibility, references and some recommended usage patterns.

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.