Tamizh in words

Bootstrapping Clojure Project Using Mount And Aero

Published on
Read Time
· 4 min read

In this blog post, we are going to focus on bootstrapping the Clojure project using Mount & Aero and interacting with the application using the REPL.

This blog post is a part 1 of the blog series Building an E-Commerce Marketplace Middleware in Clojure.

Getting Started

Let's get started by creating a new app project using Leiningen and give it a project name wheel.

lein new app wheel

Reading Configuration

To manage the application-level configuration, we are going to use Aero.

; project.clj
(defproject wheel "0.1.0-SNAPSHOT"
; ...
:dependencies [[org.clojure/clojure "1.10.1"]
[aero "1.1.3"]]
; ...
)

Aero expects us to specify the configuration as a Clojure map in an EDN file. So, let's create a config.edn in the resources directory and add configuration map with the database configuration entry.

{:app
{:database
{:adapter "postgresql"
:username #or [#env "WHEEL_APP_DB_USERNAME" "postgres"]
:password #or [#env "WHEEL_APP_DB_PASSWORD" "postgres"]
:database-name #or [#env "WHEEL_APP_DB_NAME" "wheel"]
:server-name #or [#env "WHEEL_APP_DB_SERVER" "localhost"]
:port-number #or [#env "WHEEL_APP_DB_PORT" 5432]}}}

When Aero reads this map, it tries to populate the configuration parameters from the environment variables and use the defaults if the corresponding environment variable not found.

To read this configuration in our application, Let's add a new file config.clj under a folder infra.

> mkdir src/wheel/infra
> touch src/wheel/infra/config.clj

Then add a new function to read this configuration.

; infra/config.clj
(ns wheel.infra.config
(:require [aero.core :as aero]
[clojure.java.io :as io]))

(defn- read-config []
(aero/read-config (io/resource "config.edn")))

Now we need to read this configuration during the application bootstrap before starting anything else. It's where Mount comes into the picture.

; project.clj
(defproject wheel "0.1.0-SNAPSHOT"
; ...
:dependencies [ ; ...
[mount "0.1.16"]]
; ...
)

With Mount added as dependency, let's define a Mount state called root

; infra/config.clj
(ns wheel.infra.config
(:require ; ...
[mount.core :as mount]))

(defn- read-config []
(aero/read-config (io/resource "config.edn")))

(mount/defstate root
:start (read-config))

Then add a new function database to expose the database configuration,

; infra/config.clj
; ...
(defn database []
(get-in root [:app :database]))

By defining an individual function(s) like this for each sub-system configuration, we are decoupling the implementation of the configuration from the downstream parts of the system which require this configuration.

Verifying using REPL

To verify our implementation so far, let's start the REPL

> lein repl

Inside the REPL, load and switch to the wheel.infra.config namespace

wheel.core=> (load "wheel/infra/config")
nil
wheel.core=> (in-ns 'wheel.infra.config)
#object[clojure.lang.Namespace 0x7d26cd65 "wheel.infra.config"]

Then start Mount, which will in-turn start the config/root state.

wheel.infra.config=> (mount/start)
{:started ["#'wheel.infra.config/root"]}

Now we can call the database function to get the database configuration.

wheel.infra.config=> (database)
{:adapter "postgresql", :username "postgres", ...}

This approach is one of the least effective ways to interact with the REPL without any editor/IDE support.

Let's have a look at how do we do it in VS Code.

VS Code Calva-REPL

The VS Code Calva Extension provides a very good development experience for developing Clojure applications in VS Code.

To use the REPL in VS Code, open the project and issue the command Calva: Start a REPL project and connect (ctrl+alt+c ctrl+alt+j).

In the project type prompt, select Leiningen and then in the profiles prompt, select none.

When Calva has connected, it will open a REPL window.

Then open the config.clj file and issue the command Calva: Load (evaluate) current file and its dependencies (ctrl+alt+c enter). It will load the wheel.infra.config namespace in the REPL.

We can type in the code as we did in the previous section. However, typing directly in the REPL is not a recommended practice. Usually, we will use the comment special form and type in the individual expressions that we want to explore in the REPL.

; config.clj
; ...
(comment
(mount/start)
(database))

To verify the output, place the cursor at the end of (mount/start) and issue the command Calva: Send Current Form to REPL wind and evaluate it (alt+ctrl+c alt+e). We will get output like below in the REPL window.

wheel.infra.config=> (mount/start)
{:started ["#'wheel.infra.config/root"]}

We can do a similar thing to verify the database function.

wheel.infra.config=> (database)
{:adapter "postgresql", :username "postgres", ...}

We can stop the application by calling the mount's stop function.

; config.clj
; ...
(comment
(mount/stop))

Summary

In this blog post, we created a new Clojure project using Leiningen, added the application-level configuration using Aero and bootstrapped the configuration reading part using Mount. We also learnt how to interact with the application using the REPL.

The source code is available on this GitHub repository.


Did the content capture your interest? Stay in the loop by subscribing to the RSS feed and staying informed!