In this section we will discuss some of the best practices managing configurations for your applications.
Firstly, define a default configuration, document the various configuration options and set defaults which are best suited for a production environment. Don't put secrets in here!!!. If you have username, passwords, or appilcation keys don't add them in the defaults values (see database username/password). A good default configuration should contain good default values for all the configurable properties with the exception of secrets and endpoints of dependencies. In such way the a typical user of your application will only have to enter the secrets in most of the cases.
The following is a made-up example of an application which uses a database and exposes some api.
(ns your.namespace
(:require [com.brunobonacci.oneconfig :refer :all]
[clojure.java.io :as io]
[clojure.string :as str]))
;; default configuration, values defined here
;; should be the one you wish to use in a
;; production environment.
(def ^:const DEFAULT-CONFIG
{ ;; HTTP server listener configuration
:server {:port 80 :bind "0.0.0.0"}
;; database connection configuration
:database {:host "mydatabase" :port 1234
:dbname "mydata"
;; REQUIRED!!!
;;:user "username"
;;:pasword "secret"
:use-encryption true
:connection-pool-size 10}
;; application limits
:limits {:max-items-x-page 25
:max-login-attempt 5
:max-session-time 300000
:max-idle-time 60000
:max-upload-size 8000000}
;; Deep storage configuration
:media-storage {:type :s3
:bucket "my-media-storage"
:prefix "media/storage/"}
})
Once you defined a good default configuration you need to retrieve
which environment you are running on. This information might come from
different places and it mostly depends on how you deploy your
software. Let's assume that we have environment variable called $ENV
(could be different) which contains the current environment, if the
environment variable is not present then we assume a developer
machine.
(defn env
"returns the current environmet the system is running in.
This has to be provided by the infrastructure"
[]
(or (System/getenv "ENV") "dev"))
Next we need to retrieve which version our system is currently on.
Again you might store this information in various way, the simplest
one which I recommend to use is to store it in a resource file in your
resources/
folder, something like <your-project-name>.versoin
and
then read it from your system as below. This approach allows you to
have only one place where you write the version number as you can use
the same file for your project.clj
version. Please see the
following link for a leiningen example: https://github.com/BrunoBonacci/1config/blob/master/1config-core/project.clj
;;
;; Better to store the version of the project as a resource file
;;
(defn version
"returns the version of the current version of the project
from the resource bundle."
[]
(some->> (io/resource "my-project.version")
slurp
str/trim))
Finally, you can retrieve the user configuration and merge it with the default configuration as below:
;; Overall system config
(defonce config
(->> (configure {:key "system1" :env (env) :version (version)})
(deep-merge DEFAULT-CONFIG)))
Assuming that the user configuration looks like this:
{:server {:port 9000}
:database {:user "testuser" :password "testpass"}}
Then the final, overall config
will look like the following:
{:server {:port 9000, :bind "0.0.0.0"},
:database
{:host "mydatabase",
:port 1234,
:dbname "mydata",
:use-encryption true,
:connection-pool-size 10,
:user "testuser",
:password "testpass"},
:limits
{:max-items-x-page 25,
:max-login-attempt 5,
:max-session-time 300000,
:max-idle-time 60000,
:max-upload-size 8000000},
:media-storage
{:type :s3, :bucket "my-media-storage", :prefix "media/storage/"}}
Please note that the :port
, the :user
and the :password
reflect
the user choice while the other keys are as defined in the
DEFAULT-CONFIG
.
Now to set the user configuration you could use the 1cfg
command
line tool to set the value, however it is recommended that the you
keep the dev
configuration only on your machine and you don't set it
in the shared DynamoDB table. The reason why this is best is because
different developers might need different configurations.
Luckily, 1Config solves this problem by allowing you to have a
file-system configuration which overrides values from the DynamoDB
(see configuration providers below). There are many options, however
the best suited for development purposes is to create a file in your
home directory with the following template ~/.1config/<service-key>/<env>/<version>/<service-key>.<ext>
# template ~/.1config/<service-key>/<env>/<version>/<service-key>.<ext>
mkdir -p ~/.1config/system1/dev/1.0.0/
# and create a fine with your configuration inside
cat > ~/.1config/system1/dev/1.0.0/system1.edn <<\EOF
;; my dev config
{:server {:port 9000}
:database {:user "testuser" :password "testpass"}}
EOF
With this configuration file in place 1Config will use as value for the configuration ignoring possible other matching entries in the dynamo table.
It is possible to test it via the command line tool:
$ 1cfg GET -k system1 -e dev -v 1.0.0
;; my dev config
{:server {:port 9000}
:database {:user "testuser" :password "testpass"}}
The advantage of this approach, is that the configuration lives outside of the project directory structure so it won't be committed in your version control system accidentally revealing your secrets to other people.
It is good practice to keep the development environment separated from
the other environments. You can pick a name/label like dev
or
local
(or anything else) and just use it for your local development.
For development purposes, if you wish to use only the filesystem based
configuration provider (see below) you can either set the environment
variable ONECONFIG_DEFAULT_BACKEND
or the JVM system property
1config.default.backend
with the value fs
(for example:
export ONECONFIG_DEFAULT_BACKEND=fs
to be used for development
purposes only).
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close