Init is a small and flexible framework for application initialization and dependency injection.
A configuration defines the components of your system and how they depend on each other. A configuration is an ordinary Clojure map, and components can be defined as ordinary maps as well:
(def config
{:http/port {:name :http/port
:start-fn (constantly 8080)}
:ring/handler {:name :ring/handler
:start-fn #'my-app.handler.handle-request}
:http/server {:name :http/server
:deps [:ring/handler :http/port]
:start-fn #'my-app.http.start-server}})
Since it is just data, you can easily build your configuration from other formats, inspect and transform your configuration, transfer it via the wire, etc.
Init encourages you to write loosely coupled components as plain old Clojure vars. Applying dependency inversion, you write simple functions that take required values as arguments:
(defn start-server [handler port]
(httpkit/run-server handler {:port port}))
Configure your components with metadata on the var, right where you write the code:
(defn start-server
{:init/inject [:ring/handler :http/port]}
[handler port]
(httpkit/run-server handler {:port port}))
Metadata is just data and does not require your code to depend on init's namespaces. Your code stays idiomatic Clojure, you can test it without special mechanisms, and can use it completely without init if you want.
You can annotate library code to make it easy to use with init, without forcing a framework on your users.
Init's metadata also serves as documentation.
Write less "glue code" by declaring how your dependencies should be injected into your component.
For example, consider a Ring handler that requires a database connection. Using dependency inversion, you would wrap your handler in a constructor function:
(defn handle-request
{:init/inject [:app/db]}
[database]
(fn [request]
(resp/response (query database))))
Alternatively, you could use partial application to bind the database
argument:
(defn handle-request
{:init/inject [:partial :app/db]}
[database request]
(resp/response (query database)))
This instructs init to partially apply your function on start, so that your component will be a valid one-argument Ring handler.
Finally, you could decide to take your dependencies in the request
itself,
as if it would be provided by Ring middleware. Init has special support for
that as well:
(defn handle-request
{:init/inject [:into-first {:db :app/db}]}
[request]
(resp/response (query (:db request))))
Init can automatically scan your classpath for Clojure namespaces that define components:
;; Find all namespaces with prefix "my-app" on the classpath, and build
;; a config map:
(defn config (init.discovery/scan ['my-app]))
You can do so at runtime or at compile time. In the latter case, your
application will have very little overhead compare to hand-written code,
and does not require libraries such as clojure.tools.namespace
:
(defn config (init.discovery/static-scan ['my-app]))
Organise your components by tags, and declare injections using tags. This allows you to decouple your components, and to find all components providing a certain functionality in the system:
(defn database-healthy?
{:init/tags #{:health/checker}
:init/inject [:app/db]}
[db]
(fn [] (heartbeat-query db)))
(defn message-broker-healthy?
{:init/tags #{:health/checker}
:init/inject [:events/broker]}
[broker]
(fn [] (ping broker)))
(defn health-endpoint
{:init/inject [:partial #{:health/checker}]}
[checkers request]
(if (every? (fn [healthy?] (healthy?)) checkers))
(resp/ok {:status :healthy})
(resp/service-unavailable {:status :unhealthy}))
In addition to tagging, you can use Clojure's default hierarchy and derive
to declare is-a relationships, as Init uses isa?
to find matching
components.
(defn start-server []
(http/run-server))
(derive ::start-server :http/server)
Similarly, you can also use Java classes as tags, and find all components that provide a certain class or one of its subclasses.
Once you have a configuration, leave it to Init to start all your components in the correct order and build a system map.
When your application shuts down, Init will stop your components in reverse dependency order, making sure all resources are released properly.
Init is a fairly small library and has very few dependencies. At the moment,
it only requires weavejester/dependency
.
Other dependency are optional and users will need to provide them when they want to use their functionality:
org.clojure/tools.namespace
for classpath scanningcom.fbeyer/autoload
for service-loader style discoveryInit is modular in design:
init.config
is agnostic to how the
configuration is built.Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close