core
core copied to clipboard
The core library of the Duct framework
Duct core
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.0"]
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/constis a component that returns its value when initialized.:duct/moduledenotes a Duct module (see the modules section).:duct/profiledenotes a Duct profile (see the profiles section).:duct.core/environmentspecifies the environment of the configuration, and may be set to:developmentor:production. It does nothing on its own, but may be used by modules.:duct.core/project-nsspecifies 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 theresources/<project-ns>/publicdirectory.
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/displaceis equivalent to^:displace, but works with primitive values#duct/replaceis 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/baseis the base of all new profiles:duct.profile/devis a profile for development:duct.profile/localis a profile only on your local development machine:duct.profile/testis a profile for testing:duct.profile/prodis 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.