duct-framework / core

The core library of the Duct framework
76 stars 21 forks source link
clojure duct framework integrant

Duct core

Build Status

The core of the next iteration of the Duct framework. It extends the Integrant micro-framework with support for modules, asset compilation and environment variables.

Installation

To install, add the following to your project :dependencies:

[duct/core "0.8.1"]

Usage

First we need to read in an Integrant configuration from a file or resource:

(require '[clojure.java.io :as io]
         '[duct.core :as duct])

(defn get-config []
  (duct/read-config (io/resource "example/config.edn")))

Once we have a configuration, we have three options. The first option is to prep-config the configuration, which will load in all relevant namespaces and apply all modules.

This is ideally used with integrant.repl:

(require '[integrant.repl :refer :all])

(set-prep! #(duct/prep-config (get-config)))

Alternatively we can prep-config then exec-config the configuration. This initiates the configuration, then blocks the current thread if the system includes any keys deriving from :duct/daemon. This is designed to be used from the -main function:

(defn -main []
  (-> (get-config) (duct/prep-config) (duct/exec-config)))

You can change the executed keys to anything you want by adding in an additional argument. This is frequently used with the parse-keys function, which parses keywords from command-line arguments:

(defn -main [& args]
  (let [keys (or (duct/parse-keys args) [:duct/daemon])]
    (-> (get-config)
        (duct/prep-config keys)
        (duct/exec-config keys))))

This allows other parts of the system to be selectively executed. For example:

lein run :duct/compiler

Would initiate all the compiler keys. And:

lein run :duct/migrator

Would initiate the migrator and run all pending migrations. If no arguments are supplied, the keys default to [:duct/daemon] in this example.

Keys

This library introduces a number of Integrant components:

Readers

This library also introduces five new reader tags that can be used in Duct configurations:

Modules

Modules are Integrant components that initialize into a pure function. This function expects a configuration as its argument, and returns a modified configuration.

Most modules derive from :duct/module. This both identifies them, and ensures they are executed after profiles.

Here's a simple example module:

(require '[integrant.core :as ig])

(derive :duct.module/example :duct/module)

(defmethod ig/init-key :duct.module/example [_ {:keys [port]}]
  (fn [config]
    (assoc-in config [:duct.server.http/jetty :port] port)))

This above module updates the port number of the :duct.server.http/jetty key. By itself this isn't hugely useful, but modules can be made to update many different components at once.

Modules can also have dependencies, achieved using integrant.core/prep-key:

(defmethod ig/prep-key :duct.module/example [_ options]
  (assoc options ::requires (ig/ref :duct.module/parent)))

This adds a reference to the module's options, ensuring that Integrant will initialize the :duct.module/parent module before :duct.module/example.

You can also have optional dependencies with integrant.core/refset:

(defmethod ig/prep-key :duct.module/example [_ options]
  (assoc options ::requires (ig/refset #{:duct.module/parent})))

Profiles

A profile is (currently) a type of module that merges the value of the key into the resulting configuration.

There are five profile keys included in this library:

Documentation

License

Copyright © 2020 James Reeves

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.