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.