Configure REPL on Startup

A Clojure REPL starts in the user namespace by default. Clojure will automatically load code from a user.clj file into the REPL to carry out common startup tasks.

  • load project code into the REPL by requiring namespaces
  • set the default namespace with in-ns
  • call functions to run an application or service
  • start components (i.e for mount, component, integrant)

HINT:: Example project

practicalli/clojure-configure-repl project contains example code for configuring the REPL start up

Create a dev/user.clj file and :dev alias

Create a dev/user.clj file with a namespace called user.

user.clj should include a namespace definition

  1. (ns user)

practicalli/clojure-deps-edn includes a :dev alias which adds the dev directory to the project classpath. Alternatively, edit the deps.edn file and add the following code:

  1. :dev
  2. {:extra-paths ["dev"]}

Running a Clojure REPL with the -A:dev alias will make the dev/user.clj file available to be loaded by the REPL.

In this example the dev/ path is added to the project and then the REPL is run using rebel readline.

  1. clojure -A:dev:rebel

Hint::Using the dev/ directory

The user.clj code should not be included in live deployments, such as jars and uberjars. Including the dev/ directory via the :dev alias keeps the user.clj and any other development only code separate from deployment actions.

Requiring namespaces

By requiring a namespace in the dev/user.clj file, the code defined in that namespace will be loaded into the REPL once started.

Add a require expression to the namespace definition in dev/user.clj

  1. (ns user
  2. :require [practicalli.project-namespace])

Require loads all the expressions into the REPL, so functions are immediately available.

Calling functions

Functions from the required namespace can be called, to start the application for example.

  1. (ns user
  2. :require [practicalli.project-namespace])
  3. (practicalli.project-namespace/-main)

Changing Namespaces

When using the REPL directly it can be changed into a specific namespace using the in-ns function.

Changing to the project namespace allows the -main namespace to be called

  1. (ns user
  2. :require [practicalli.project-namespace])
  3. (in-ns 'practicalli.project-namespace)
  4. (-main)

Starting Component Lifecycle Services

Clojure has several library to manage the lifecycle of components that make up the application, especially those components with state. Components can be started and stopped in a specific order.

Example component lifecycle libraries included

In Clojure it is idiomatic to define the component lifecyle services in a namespace called dev. In the def/user.clj file, add the following ns declaration to require the dev namespace and change to that namespace with in-ns

  1. (ns user
  2. (:require [dev]))
  3. (in-ns 'dev)

Now define code in the dev/dev.clj file that controls the component lifecycle services library for the project.

Example project with component lifecycle

{% tabs mount=”Mount”, integrant=”Integrant”, component=”Component” %}

{% content “mount” %} Using mount, its common to define a dev.clj file with go, stop and restart functions that manage the lifecycle of mount components. A start function contains the list of components with optional state.

Require the mount namespace and the main namespace for the project, which should contain all the code to start and stop services.

  1. (ns user
  2. :require [mount.core :refer [defstate]]
  3. [practicalli.app.main])
  1. (defn start []
  2. (with-logging-status)
  3. (mount/start #'practicalli.app.conf/environment
  4. #'practicalli.app.db/connection
  5. #'practicalli.app.www/business-app
  6. #'practicalli.app.service/nrepl))

The go function calls start and marks all components as ready.

  1. (defn go
  2. "Start all states defined by defstate"
  3. []
  4. (start)
  5. :ready)

The stop function stops all components, removing all non-persistent state.

  1. (defn stop [] (mount/stop))

The reset function that calls stop, refreshes the namespaces so that stale definitions are removed and starts all components (loading in any new code).

  1. (defn reset
  2. "Stop all states defined by defstate.
  3. Reload modified source files and restart all states"
  4. []
  5. (stop)
  6. (namespace/refresh :after 'dev/go))

Hint::Use dev namespace during development

Require practicalli.app.dev namespace rather than main, to start components in a development environment.

{% content “integrant” %}

TODO: pull requests accepted

{% content “component” %}

TODO: pull requests accepted

{% endtabs %}

Reference