You can configure components using metadata on vars:
If you prefer, check out Init's specs in the init.specs
namespace.
Vars can be interpreted as components. When scanning namespaces, init will only pick up vars that have at least one supported metadata key.
Vars without metadata can be components when they are either constants or nullary functions. Otherwise, init will not know which values to supply as arguments when starting the component.
The default component name is the qualified var name, converted to keyword.
(defn my-component [])
; => #'user/my-component
(:name (init.meta/component #'my-component))
; => :user/my-component
The name can be specified explicitly using the :init/name
metadata,
and must be a qualified keyword or symbol.
(defn my-component {:init/name ::foo} [])
; => #'user/my-component
(:name (init.meta/component #'my-component))
; => :user/foo
:init/name
can also be used to tag vars as components, using the default name:
(defn ^:init/name my-component [])
Additional tags can be specified with the :init/tags
metadata:
(defn start-server
{:init/tags #{:http/server :app/service}}
[handler]
(httpkit/run-server handler))
Init also treats type hints as tags, so that you can inject components by Java type.
You can specify selectors for components to inject via the :init/inject
metadata key. In its basic form, this is a vector with one element for each
function argument.
Selectors can be qualified names (keywords or symbols) or collections of qualified names. For collections, a component needs to provide all tags in the collection to be eligible for injection.
In the following example, the ::server
component requires two components.
The first one needs to provide :ring/handler
, the second both :server/port
and :env/prod
:
(defn server
{:init/inject [:ring/handler [:server/port :env/prod]]}
[handler port]
(httpkit/run-server handler {:port port}))
Most of the time, dependencies are considered unique and must match exactly one component in the configuration. Otherwise, it would be an error.
You can specify that a dependency can take zero or more matching components as a set, by putting the required tags in a set:
(defn router
{:init/inject [#{:reitit.route/data}]}
[routes]
(reitit/router (vec routes)))
Instead of tags, you can also refer to a component var. This is equivalent to depend on a component with the same name as the referred var, but might give a better developer experience as analysis tools will no longer mark component vars as unused, and support updating references when refactoring.
(defn root-handler
{:init/inject [#'router #'default-handler]}
[router]
(reitit/ring-handler router default-handler))
You can inject multiple dependencies as a map, using tags as keys:
[:keys val+]
.
This component has two dependencies, ::foo
and ::bar
, and will get them
injected as a map:
(defn injecting-keys
{:init/inject [[:keys ::foo ::bar]]}
[m]
(println "Received foo:" (::foo m) "and bar:" (::bar m)))
For more advanced selection and full control over the maps's key, use the map form:
(defn injecting-maps
{:init/inject [{:db [:app/db :profile/prod]}]}
[m]
(query (:db m)))
This also supports arbitrary nesting.
:get
A typical use case is to define a component that loads some configuration from a configuration file or the environment into a map, and to require keys from configuration in a component.
For that, you can use the :get
form [:get selector k+]
, taking a component
selector selector
and one or more keys k
. Multiple keys form a path as
understood by get-in
.
(defn load-config
{:init/name :app/config}
[]
{:http {:port 8080}})
(defn start-server
{:init/inject [:ring/handler [:get :app/config :http :port]]}
[handler port]
(httpkit/run-server handler {:port port}))
:apply
For the case where Init does not provide what you need, you can transform
injected values with Clojure functions using [:apply f selector*]
:
(defn inject-with-apply
{:init/inject [[:apply str/lower-case ::string-component]]}
[s]
(print s))
For function components, you can instruct Init to combine injected values with runtime arguments.
You can bind left-most arguments to injected values using a partial
application: [:partial selector*]
.
(defn lookup
{:init/inject [:partial :app/db]
[db id]
(find-entity db id)})
At runtime, your ::lookup
component will take one argument, id
, while
the db
will be bound to the value provided by the :app/db
component.
Init can also inject values into collection arguments, merging them with
runtime values using (into runtime-arg injected)
. For this to make sense,
the injected value needs to be a collection itself.
There are two variants for this:
[:into-first selector]
adds the injected value into the first argument[:into-last selector]
adds the injected value into the last argument(defn ring-handler
{:init/inject [:into-first {:db :app/db}]}
[request])
(defn http-request
{:init/inject [:into-last [:keys :http/client]]}
[url opts])
If your component needs to perform cleanup task when the system is stopped, you can supply a stop function.
Stop functions take one argument: the component value to stop.
There are two ways to do so: :init/stop-fn
defines a function directly on the
component var, and :init/stops
declares that the var having this metadata is
the stop function for an existing component.
(declare stop-server)
(defn start-server
{:init/stop-fn #'stop-server}
[]
(server/start))
(defn stop-server [server]
(server/stop server))
You can specify the function as:
The :init/stops
key must be a keyword, symbol or var referencing an existing
component:
(defn ^:init/name start-server []
(server/start))
(defn stop-server
{:init/stops #'start-server}
[server]
(server/stop server))
Using vars is preferred for both, as they will survive refactoring.
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close