Skip to content

Latest commit

 

History

History
180 lines (129 loc) · 5.34 KB

README.md

File metadata and controls

180 lines (129 loc) · 5.34 KB

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:

  • :duct/const is a component that returns its value when initialized.
  • :duct/module denotes a Duct module (see the modules section).
  • :duct/profile denotes a Duct profile (see the profiles section).
  • :duct.core/environment specifies the environment of the configuration, and may be set to :development or :production. It does nothing on its own, but may be used by modules.
  • :duct.core/project-ns specifies the base namespace of your project. This is often used by modules for determining where to put things. For example, public web resources are typically placed in the resources/<project-ns>/public directory.

Readers

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

  • #duct/env "VARNAME" allows an environment variable to be referenced in the configuration.
  • #duct/include "path" will include replace the tag with the resource at the specified path. This allows a configuration to be split up into separate files.
  • #duct/resource "path" will convert a resource path into a URL
  • #duct/displace is equivalent to ^:displace, but works with primitive values
  • #duct/replace is equivalent to ^:replace, but works with primitive values

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:

  • :duct.profile/base is the base of all new profiles
  • :duct.profile/dev is a profile for development
  • :duct.profile/local is a profile only on your local development machine
  • :duct.profile/test is a profile for testing
  • :duct.profile/prod is a profile for production

Documentation

License

Copyright © 2020 James Reeves

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