1 Introduction
1.1 Overview
std.lib.component is part of the standard foundation library set. This page collects the public API reference for the namespace.
2 Walkthrough
2.1 Defining and running a component
A component is any value that implements std.protocol.component/IComponent. Records are the most common choice. start and stop run the lifecycle, and with ensures cleanup.
start, query, and stop a component
^{:refer std.lib.component/start :added "3.0"}
(defrecord Database []
protocol.track/ITrack
(-track-path [db] [:my-app :database])
protocol.component/IComponent
(-start [db] (assoc db :status "started"))
(-stop [db] (dissoc db :status))
(-health [db] {:status :ok}))
(start (Database.))
=> {:status "started"}
^{:refer std.lib.component/health :added "3.0"}
(health (Database.))
=> {:status :ok}
^{:refer std.lib.component/stop :added "3.0"}
(stop (start (Database.)))
=> {}
use with to manage component lifecycle
^{:refer std.lib.component/with :added "3.0"}
(with [db (Database.)]
(started? db))
=> true
2.2 Tracking components
std.lib.component.track keeps a global registry of live components. Tracked components can be listed, queried, and acted upon in groups.
track and query a component
^{:refer std.lib.component.track/track :added "3.0"}
(def db (-> (Database.) start))
^{:refer std.lib.component.track/tracked? :added "3.0"}
(tracked? db)
=> true
^{:refer std.lib.component.track/tracked :added "3.0"}
(track/tracked [:my-app :database] info)
=> map?
3 API
- *kill*
- all-props
- component?
- get-options
- get-prop
- health
- impl:component
- info
- kill
- perform-hooks
- primitive?
- remote?
- set-prop
- start
- started?
- stop
- stop-raw
- stopped?
- with
- with-lifecycle
- wrap-start
- wrap-stop
v 3.0
(defn all-props
([component]
(try (keys (protocol.component/-props component))
(catch AbstractMethodError e))))
link
(all-props (Database.)) => [:interval]
v 3.0
(defn component?
([obj]
(h/suppress (extends? protocol.component/IComponent (type obj)))))
link
(component? (Database.)) => true
v 3.0
(defn get-options
([component opts]
(let [mopts (try (protocol.component/-get-options component)
(catch IllegalArgumentException e)
(catch AbstractMethodError e))]
(merge mopts opts))))
link
(get-options (Database.) {:init (fn [x] 1)}) => (contains {:init fn?})
v 3.0
(defn get-prop
([component k]
(let [getter (-> (protocol.component/-props component)
(get-in [k :get]))]
(getter))))
link
(get-prop (Database.) :interval) => 10
v 3.0
(defn health
([component]
(try (protocol.component/-health component)
(catch AbstractMethodError e (throw e))
(catch Throwable t {:status :errored}))))
link
(health (Database.)) => {:status :ok}
v 3.0
(defn impl:component
([{:keys [name]}]
(symbol "std.lib.component" (subs (clojure.core/name name) 1))))
link
(impl:component {:name ':-foo}) => 'std.lib.component/foo
v 3.0
(defn info
([component]
(info component :default))
([component level]
(try (protocol.component/-info component level)
(catch IllegalArgumentException e {})
(catch AbstractMethodError e {}))))
link
(info (Database.)) => {:info true}
kill ^
[component] [component opts]
kills a systems, or if method is not defined stops it
v 3.0
(defn kill
([component]
(kill component {}))
([component opts]
(binding [*kill* true]
(stop component opts))))
link
(kill (start (Database.))) => {}
v 3.0
(defn perform-hooks
([component functions hook-ks]
(reduce (fn [out k]
(let [func (or (get functions k)
identity)]
(func out)))
component
hook-ks)))
link
(perform-hooks (Database.) {:init (fn [x] 1)} [:init]) => 1
v 3.0
(defn primitive?
([x]
(or (string? x)
(number? x)
(boolean? x)
(h/regexp? x)
(uuid? x)
(uri? x)
(h/url? x))))
link
(primitive? 1) => true (primitive? {}) => false
v 3.0
(defn remote?
([component]
(try (protocol.component/-remote? component)
(catch AbstractMethodError e false))))
link
(remote? (Database.)) => false
v 3.0
(defn set-prop
([component k value]
(let [setter (-> (protocol.component/-props component)
(get-in [k :set]))]
(setter value))))
link
(set-prop (Database.) :interval 3) => (throws)
v 3.0
(defn start
([component]
(start component {}))
([component opts]
(let [{:keys [setup hooks functions]} (get-options component opts)
{:keys [pre-start post-start]} hooks
setup (or setup identity)
component (-> component
(perform-hooks functions pre-start)
(protocol.component/-start)
(setup)
(perform-hooks functions post-start))
component (track/track component)]
component)))
link
(start (Database.)) => {:status "started"}
v 3.0
(defn started?
([component]
(try (protocol.component/-started? component)
(catch IllegalArgumentException e true)
(catch AbstractMethodError e true))))
link
(started? 1) => true (started? (start {})) => true (started? (Database.)) => true
v 3.0
(defn stop
([component]
(stop component {}))
([component opts]
(let [component (track/untrack component)
{:keys [teardown hooks functions]} (get-options component opts)
{:keys [pre-stop post-stop]} hooks
teardown (or teardown identity)
component (-> component
(perform-hooks functions pre-stop)
(teardown)
(stop-raw)
(perform-hooks functions post-stop))]
component)))
link
(stop (start (Database.))) => {}
v 3.0
(defn stop-raw
([component]
(if *kill*
(try (protocol.component/-kill component)
(catch IllegalArgumentException e
(protocol.component/-stop component))
(catch AbstractMethodError e
(protocol.component/-stop component)))
(protocol.component/-stop component))))
link
(stop-raw (start (Database.))) => {}
v 3.0
(defn stopped?
([component]
(try (protocol.component/-stopped? component)
(catch IllegalArgumentException e false)
(catch AbstractMethodError e false))))
link
(stopped? 1) => false (stopped? {}) => false (stopped? (start {})) => false (stopped? (Database.)) => false
v 3.0
(defmacro with
([[var expr & more] & body]
`(let [~var ~expr
~var (start ~var)]
(try
~(if (empty? more)
`(do ~@body)
`(with [~@more] ~@body))
(finally (stop ~var))))))
link
(with [db (Database.)] (started? db)) => true
with-lifecycle ^
[[var {:keys [start stop]} & more] & body]
helper for lifecycle management
v 4.1
(defmacro with-lifecycle
[[var {:keys [start stop]} & more] & body]
`(let [~var ~start]
(try
~(if (empty? more)
`(do ~@body)
`(with-lifecycle [~@more] ~@body))
(finally (~stop ~var)))))
link
(def out (atom [])) (with-lifecycle [a {:start (do (swap! out conj :start) :resource) :stop (fn [x] (swap! out conj [:stop x]))}] (swap! out conj [:body a]) :done) => :done @out => [:start [:body :resource] [:stop :resource]]
v 4.0
(defn wrap-start
([f steps]
(fn [rt]
(h/-> rt
(reduce (fn [rt {:keys [key setup]}]
(if-let [data (get rt key)]
(setup rt data)
rt))
%
(filter :setup steps))
(f)
(reduce (fn [rt {:keys [key start]}]
(if-let [data (get rt key)]
(start rt data)
rt))
%
(filter :start steps))))))
link
((wrap-start (fn [rt] (assoc rt :started true)) []) {}) => {:started true}
v 4.0
(defn wrap-stop
([f steps]
(fn [rt]
(h/-> rt
(reduce (fn [rt {:keys [key stop]}]
(if-let [data (get rt key)]
(stop rt data)
rt))
%
(filter :stop steps))
(f)
(reduce (fn [rt {:keys [key teardown]}]
(if-let [data (get rt key)]
(teardown rt data)
rt))
%
(filter :teardown steps))))))
link
((wrap-stop (fn [rt] (assoc rt :stopped true)) []) {}) => {:stopped true}
4 std.lib.component: A Comprehensive Summary (including std.lib.component.track)
The std.lib.component namespace, along with its sub-namespace std.lib.component.track, provides a robust and extensible framework for managing the lifecycle of components in Clojure applications. It introduces a protocol-based approach for defining start/stop/kill operations, querying component status, and tracking component instances. This module is crucial for building modular, testable, and maintainable systems, especially in complex applications with many interconnected parts.
4.1 std.lib.component (Component Lifecycle Management)
This namespace defines the core std.protocol.component/IComponent protocol and provides functions for interacting with components that implement it. It focuses on the lifecycle (start, stop, kill) and introspection (info, health, properties) of components.
Core Concepts:
IComponentProtocol: The central abstraction for defining components. It specifies the following methods:n-start: Initializes and starts the component.n-stop: Shuts down and cleans up the component.n-kill: Forcefully terminates the component (potentially without graceful shutdown).n-info: Returns information about the component at a given level of detail.n-health: Returns the health status of the component.n-remote?: Indicates if the component connects to a remote resource.n-get-options: Retrieves component-specific options.n-props: Returns a map of component properties with getter/setter functions.n Lifecycle Hooks: Components can definesetupandteardownfunctions, as well aspre-start,post-start,pre-stop,post-stophooks, allowing for flexible initialization and cleanup.n Tracking Integration: Seamlessly integrates withstd.lib.component.trackto automatically track component instances.
Key Functions:
component?: Checks if an object implements theIComponentprotocol.nstarted?: Checks if a component is in a started state.nstopped?: Checks if a component is in a stopped state.nstart: Initiates the component's lifecycle, running pre-start hooks, the-startmethod, setup functions, and post-start hooks. It also tracks the component.nstop: Halts the component's lifecycle, running pre-stop hooks, teardown functions, the-stopmethod, and post-stop hooks. It also untracks the component.nkill: Forcefully stops a component, setting a dynamic flag*kill*totruebefore callingstop.ninfo: Retrieves information about the component.nhealth: Returns the health status of the component (e.g.,:ok,:errored).nremote?: Checks if the component interacts with remote resources.nall-props,get-prop,set-prop: Functions for inspecting and manipulating component properties defined via the-propsmethod.nwith(macro): A convenient macro for ensuring a component is started before a block of code executes and stopped afterwards, similar towith-open.n*wrap-start,wrap-stop: Higher-order functions to add additional setup/teardown steps to the start/stop process, useful for extending runtime behavior.
4.2 std.lib.component.track (Component Instance Tracking)
This sub-namespace provides a global registry for tracking component instances, allowing for introspection, management, and debugging of active components across the application.
Core Concepts:
ITrackProtocol: Components can implement this protocol to define atrack-path(a vector of keywords) where they should be registered in the global*registry*.n Global Registry (*registry*): An atom holding a nested map that storesTrackEntryrecords for all tracked components.nTrackEntry: A record that stores metadata about a tracked component, including its creation time, namespace, and the component instance itself.n* Actions: A mechanism to define and apply functions to tracked components (e.g.,stop,start,println).
Key Functions:
track-path: Retrieves the tracking path for a trackable object.ntrackable?: Checks if an object implements theITrackprotocol.ntrack: Registers a component in the global registry. It assigns a unique ID (track/id) to the component if it doesn't have one.ntracked?: Checks if a component instance is currently being tracked.nuntrack: Removes a component instance from the global registry.nuntrack-all: Clears all or a subset of tracked components from the registry.ntracked:action:add,tracked:action:remove,tracked:action:get,tracked:action:list: Functions for managing a global map of named actions that can be performed on tracked components.ntracked:all: Returns the entire*registry*or a sub-section of it.ntracked: Applies a specified action (function) to all components found at a given path in the registry.ntracked:count: Returns the count of tracked components at a given path.ntracked:locate: Finds tracked components that match specific metadata.ntracked:list: Returns a list of tracked components, optionally applying an action to each.ntracked:last: Retrieves thenmost recently tracked components at a given path.n*track:with-metadata(macro): Binds additional metadata to be associated with components tracked within its scope.
4.3 Usage Pattern:
The std.lib.component module is fundamental for:
- Application Architecture: Structuring applications into manageable, independent components.n Testing: Easily starting and stopping components for isolated testing.n Monitoring and Debugging: Gaining insight into the state and health of running components.n* Resource Management: Ensuring proper allocation and deallocation of resources.
The std.lib.component.track submodule enhances this by providing a centralized, dynamic view of all active components, enabling powerful runtime introspection and control, which is particularly valuable in long-running or complex systems.
Usage Pattern: example
;; Example of a component
(defrecord Database []
std.protocol.track/ITrack
(-track-path [db] [:my-app :database])
std.protocol.component/IComponent
(-start [db] (assoc db :status "started"))
(-stop [db] (dissoc db :status))
(-health [db] {:status :ok}))
;; Usage
(require '[std.lib.component :as comp])
(require '[std.lib.component.track :as track])
(def db-instance (-> (Database.) comp/start))
;; db-instance is now tracked and started
(comp/health db-instance)
=> {:status :ok}
(track/tracked [:my-app :database] comp/info)
=> {:my-app {:database {<uuid> {:info true, :status "started"}}}}
(comp/stop db-instance)
;; db-instance is now stopped and untracked