JBoss.orgCommunity Documentation

Chapter 5. Developing with Immutant

5.1. Introduction

Immutant fully embraces the time-honored Lisp tradition of incremental development at a REPL. This effectively puts you inside the application server, with all its integrated services at your fingertips.

It is quite an intimate experience to build your application while it's deployed and running in Immutant, with frictionless access to its services in real-time, running tests against those services immediately, as you edit them. And no mocking, packaging, or deployment steps to interrupt your flow.

5.2. In-Container Testing

At the REPL, you're free to use whichever Clojure testing library you prefer, e.g. clojure.test or Midje. Each has its own way of invoking tests, allowing you to, for example, run one at a time or perhaps run every test in one or more namespaces. These should work fine at the Immutant REPL, and you're free to refer to the integrated Immutant services in your test definitions.

There are some handy reflective functions available in the immutant.util namespace, specifically app-uri, which can be used to construct URL's in the tests for your web apps. This ensures your tests can always "find" your app, regardless of context path or HTTP port, for example.

5.2.1. lein immutant test

The test task of the lein-immutant plugin enables you to run your in-container tests when not connected to an Immutant REPL. It will find all the tests (or Midje facts) in a project, fire up an Immutant instance, deploy the project to it, connect to its REPL, run all the tests, undeploy the app, shutdown the Immutant, and display the results, returning success only if all tests pass.

Because it conveniently runs all your tests inside the app server, a successful run yields a high confidence that your code will run correctly when it counts – in production, when deployed to the same app server. For this reason, it may also be useful to run it on your app's Continuous Integration host whenever any changes are committed.

Note: the log output for the Immutant instance used for the test run will be located in <project-root>/target/isolated-immutant/standalone/log/.

5.3. The REPL

Immutant provides support for connecting to an application's runtime via nREPL.

Each REPL service runs on a unique port, allowing you to have REPLs into multiple applications within the same Immutant, or multiple REPLs within the same application.

REPL services can be started at application deploy time, or dynamically from application code. REPLs started dynamically can be also be shutdown dynamically. REPLs started at deploy time and any dynamically started REPLs that are not shut down by the application will be shut down when the application is undeployed.

See initialization configuration for a list of configuration options (like :nrepl-port) and how to apply them.

5.3.1. Binding To An Interface

AS7 is setup to use multiple network interfaces, and allows you to specify different interfaces for your server: public, management, and unsecure. By default, these interfaces are all the same - 127.0.0.1. But you can specify different addresses for each named interface, aiding in the security setup of your management ports.

By default, the repl services started by Immutant bind to the management interface.

5.3.2. nREPL

nREPL is a client/server protocol that provides a Clojure REPL across a network. It has several clients that support it.

5.3.2.1. Starting nREPL

You can start an nREPL service using one (or both) of two methods: specifying :nrepl-port and/or :nrepl-interface options in your configuration, or by calling immutant.repl/start-nrepl from application code.

5.3.2.2. Starting nREPL via configuration

To have Immutant start a nREPL service on your behalf, you can specify one or both of the following options:

OptionDefaultDescription
:nrepl-portnone, 0 if :dev profile is active or :nrepl-interface providedThe port for binding the nREPL endpoint. 0 means "any available port"
:nrepl-interfacenone, :management if :nrepl-port providedCan be one of :public, :management, or :unsecure, or an ip address string. See the section on interface aliases.

If :nrepl-port is set to 0, the choice of an available port is delegated to the operating system. Immutant will start up an nREPL service using that port, and write it to a file beneath your project's target/ directory called repl-port and also to a file in the root of the project called .nrepl-port, following Leiningen's convention. Many nREPL clients will use one of these files to establish a connection. Both will be deleted when the Immutant process exits.

When the :dev profile is active, :nrepl-port defaults to 0. This means that, by default, every application you deploy will have its own dedicated nREPL, and most nREPL clients will automatically detect and connect to it using the files mentioned above.

To prevent the nREPL server from starting by default, simply deploy your application without the :dev profile active, or set :nrepl-port to nil.

Immutant also honors any lein-ring options specified in your project.clj. These options will only be used if no :nrepl-port is specified in your configuration. If :start? is falsey, no nREPL will be started even when the :dev profile is active.

5.3.2.3. Starting nREPL from code

You can also start an nREPL service from your own code, which is useful if you need to start it in response to a runtime event. To do so, simply call immutant.repl/start-nrepl. nREPL allows you to have multiple services in the same runtime, so you need to save the return value of start-nrepl if you want to shut down the nREPL service yourself:

;; bind to the given port on the management interface, and
;; save the service handle for later
(def nrepl (immutant.repl/start-nrepl 4242))

;; bind to the given port and interface
(immutant.repl/start-nrepl "127.0.0.1" 4242)

;; bind to the given port on the :public interface
(immutant.repl/start-nrepl :public 4242)

5.3.2.4. Connecting to nREPL

  • Via Emacs

    To connect to nREPL from Emacs, first install cider (formerly nrepl.el), then connect to your running nREPL via the nrepl function (M-x nrepl RET). It will prompt you for the host and port, and if you're somewhere beneath your project, it should default you with the correct port.

  • Via Leiningen

    Leiningen provides a REPL client (based on reply, an enhanced REPL client) that supports connecting to an nREPL service:

    # connect to an nREPL bound to port 4242 on localhost
    $ lein repl :connect 4242 
    
    # connect to an nREPL bound to port 4242 on 10.0.0.10
    $ lein repl :connect 10.0.0.10:4242 
    
    # if inside the project directory
    $ lein repl :connect
    
  • Via Counterclockwise

    Counterclockwise is an Eclipse plugin for Clojure development. You can use it to connect to your Immutant nREPL session via Window -> Connect to REPL. It will prompt you for an ip address and port to connect to.

5.3.2.5. Shutting Down nREPL

Immutant will automatically shutdown any nREPL services for you when your application is undeployed, but if you need to do so before undeploy you can do so using the immutant.repl/stop-nrepl function. You'll need to pass it the service handle returned by the start-nrepl call:

(clojure.repl/stop-nrepl nrepl)

Since you need the service handle to stop an nREPL service, you can only manually stop nREPL's you start yourself. If you start an nREPL service via the :nrepl-port configuration option, your only recourse is to allow Immutant to shut it down for you on undeploy.

5.4. Application Dependencies

Immutant provides tools to aid interactive REPL-based development via the immutant.dev namespace. Currently, it provides three functions for reloading, updating, and viewing your application's dependencies in container:

  • immutant.dev/reload-project! Resets the application's class loader to provide the paths and dependencies in the from the given project. If no project is provided, the project.clj for the appplication is loaded from disk. Also makes any new data readers from the dependencies available. Returns the project map.
  • immutant.dev/add-dependencies! Adds the given dependencies into the currently active project's dependency set and resets the application's class loader to provide the paths and dependencies from that project (via reload-project!). Each dep can either be a lein coordinate ('[foo-bar "0.1.0"]) or a path (as a String) to be added to :source-paths. Returns the project map.
  • immutant.dev/current-project Returns the map representing the currently active leiningen project. This will be the last project reloaded by reload-project!, or the map read from project.clj if reload-project! has yet to be called.

    Examples:

    ;; assuming we have an app loaded that initially only depends on clojure
    ;; (from its project.clj)
    (require '[immutant.dev :as dev]
             '[clojure.java.io :as io])
    
    (:dependencies (dev/current-project)) ; => #{[org.clojure/clojure "1.4.0"]}
    
    ;; let's add more deps
    (dev/add-dependencies! '[dep-1 "1.0.0"] '[dep/two "0.1.0-SNAPSHOT"] "extra")
    
    (:dependencies (dev/current-project)) ; => #{[org.clojure/clojure "1.4.0"] [dep-1 "1.0.0"] [dep/two "0.1.0-SNAPSHOT"]}
    
    (:source-paths (dev/current-project)) ; => [["/path/to/app/root/src", "/path/to/app/root/extra"]]
    
    ;; now let's reset the deps to those specified in project.clj
    (dev/reload-project!)
    
    (:dependencies (dev/current-project)) ; => #{[org.clojure/clojure "1.4.0"]}
    
    ;; let's add a path to :source-paths directly
    (dev/reload-project! ((dev/current-project) [:source-paths]
                          #(conj % "something")))
    
    (:source-paths (dev/current-project)) ; => [["/path/to/app/root/src", "/path/to/app/root/something"]]
    
    
Immutant 1.1.4