HomeServicesExpertiseOpen source supportAboutStoryReferencesCareerInsightsContact
Tuesday, 21 March, 2017
Miikka Koskinen
Technology

Configuring Clojure Apps

Post cover image

Last week Steven Deobald asked on Twitter how to do configuration in Clojure:

#lazyweb: student is asking: a good, concise article on “how to do configuration” in clojure? /cc @nilenso @duelinmarkers @jakemcc @ghoseb

— Steven Deobald (@deobald) March 16, 2017

In this post I'll tell you how we do it at Metosin. We've build a few web applications with a Clojure backend and a ClojureScript frontend. While there's no Metosin architecture carved in stone, some recurring patterns have emerged. Usually we structure the backend with either Component or Mount. The applications are deployed to virtual servers as uberjars using Ansible. To store the configuration, we use EDN files and we load them using Maailma.

We have two configuration files:

  • resources/config-defaults.edn contains a default values for every configuration variable. The defaults are suitable for local development. When the application is deployed, this file is included in the deployed JAR.
  • For each environment (staging/production/etc), we create a file called config-local.edn. It contains environment-specific overrides for the defaults, including things like port numbers and database credentials. This configuration file is deployed separately from the JAR.

We load the configuration with code like this:

(require '[maailma.core :as m])

(defn get-config []
  (m/build-config
    (m/resource "config-defaults.edn")
    (m/file "./config-local.edn")))

Maailma does a deep merge of the configuration maps, which makes overriding the defaults easy. For example, we might have a feature flag for enabling development tools in the application. We then want to disable them in the production environment. The configuration files could look like this:

;; config-defaults.edn
{:http {:port 3000
        :show-dev-tools? true}}

;; config-local.edn for production
{:http {:show-dev-tools? false}}

;; What the production application sees:
{:http {:port 3000
        :show-dev-tools? false}}

If needed, you can overwrite the defaults for local development by creating a config-local.edn file. Sometimes we also maintain an extra configuration file for the locally-run integration tests.

Using ComponentLink to Using Component

With Component, we pass the configuration to the parts of the system when creating them. If needed, the configuration can be also made a part of the system map:

(ns backend.system
  (:require [com.stuartsierra.component :as component]
            [backend.component.db :as db]
            [backend.component.http :as http]
            [maailma.core :as m]))

(defn get-config []
  (m/build-config
    (m/resource "config-defaults.edn")
    (m/file "./config-local.edn")))

(defn new-system []
  (let [env (get-config)]
    (component/map->SystemMap
      {:env  env
       :db   (db/create   (:db env))
       :http (http/create (:http env))})))

The configuration gets reloaded when you restart the system.

Using MountLink to Using Mount

When using Mount, we create a state to contain the configuration

(ns backend.mount.config
  (:require [mount.core :as mount :refer [defstate]]
            [maailma.core :as m]))

(deftstate config
  :start (m/build-config
           (m/resource "config-defaults.edn")
           (m/file "./config-local.edn")
           (mount/args)))

The other states can then use the configure by requiring it:

(ns backend.mount.http
  (:require [backend.mount.config :refer [config]]
            [mount.core :as mount :refer [defstate]]))

(defn start-server [port]
  ;; ...
  )

(defstate http
  :start
  (let [port (get-in config [:http :port])]
    (start-server port)))

To reload the configuration files, restart the config state. I usually reload the whole config namespace in my editor, which makes Mount restart the state.

AlternativesLink to Alternatives

Configuration is not a one-size-fits-all affair and Maailma is not the only thing out there. For another take on EDN configuration files, take a look at the aero library. If you prefer to store the configuration in environmental variables – like Heroku recommends – see environ.

Miikka Koskinen
Tuesday, 21 March, 2017

Related posts

A Christmas Present in Clojure

by Joel Kaasinen
Cover Image for A Christmas Present in Clojure

High-Level AI Strategy into a Prioritized Investment Roadmap

by Tapio Nissilä
Cover Image for High-Level AI Strategy into a Prioritized Investment Roadmap

Three Levels of AI Impact

by Tapio Nissilä
Cover Image for Three Levels of AI Impact
More

Contact

HomeServicesExpertiseOpen source supportAboutStoryReferencesCareerInsightsContact

Tampere
Hämeenkatu 13 A 5
33100 Tampere

Helsinki
Kalevankatu 13, 3rd floor
00100 Helsinki

Jyväskylä
Väinönkatu 30, 5th floor
40100 Jyväskylä

Oulu
Kirkkokatu 4 A 51
90100 Oulu

Business-id: 3374640-7
E-invoice: 003733746407
Operator: 003723327487 / Apix
metosin@skannaus.apix.fi

first.last@metosin.fi

Github
Instagram
LinkedIn
YouTube

Cookie settings