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* ^

NONE
(def ^:dynamic *kill* false)
link

all-props ^

[component]
Added 3.0

lists all props in the component

v 3.0
(defn all-props
  ([component]
   (try (keys (protocol.component/-props component))
        (catch AbstractMethodError e))))
link
(all-props (Database.)) => [:interval]

component? ^

[obj]
Added 3.0

checks if an instance extends IComponent

v 3.0
(defn component?
  ([obj]
   (h/suppress (extends? protocol.component/IComponent (type obj)))))
link
(component? (Database.)) => true

get-options ^

[component opts]
Added 3.0

helper function for start and stop

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?})

get-prop ^

[component k]
Added 3.0

gets a prop in the component

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

health ^

[component]
Added 3.0

returns the health of the component

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}

impl:component ^

[{:keys [name]}]
Added 3.0

rewrite function compatible with std.lib.impl

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

info ^

[component] [component level]
Added 3.0

returns info regarding the component

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]
Added 3.0

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.))) => {}

perform-hooks ^

[component functions hook-ks]
Added 3.0

perform hooks before main function

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

primitive? ^

[x]
Added 3.0

checks if a component is a primitive type

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

remote? ^

[component]
Added 3.0

returns whether the component connects remotely

v 3.0
(defn remote?
  ([component]
   (try (protocol.component/-remote? component)
        (catch AbstractMethodError e false))))
link
(remote? (Database.)) => false

set-prop ^

[component k value]
Added 3.0

sets a prop in the component

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)

start ^

[component] [component opts]
Added 3.0

starts a component/array/system

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"}

started? ^

[component]
Added 3.0

checks if a component has been 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

stop ^

[component] [component opts]
Added 3.0

stops a component/array/system

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.))) => {}

stop-raw ^

[component]
Added 3.0

switch between stop and kill methods

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.))) => {}

stopped? ^

[component]
Added 3.0

checks if a component has been stopped

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

with ^

[[var expr & more] & body]
Added 3.0

do tests with an active component

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]
Added 4.1

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]]

wrap-start ^

[f steps]
Added 4.0

wraps the start function with more steps

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}

wrap-stop ^

[f steps]
Added 4.0

wraps the stop function with more steps

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:

  • IComponent Protocol: 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 define setup and teardown functions, as well as pre-start, post-start, pre-stop, post-stop hooks, allowing for flexible initialization and cleanup.n Tracking Integration: Seamlessly integrates with std.lib.component.track to automatically track component instances.

Key Functions:

  • component?: Checks if an object implements the IComponent protocol.n started?: Checks if a component is in a started state.n stopped?: Checks if a component is in a stopped state.n start: Initiates the component's lifecycle, running pre-start hooks, the -start method, setup functions, and post-start hooks. It also tracks the component.n stop: Halts the component's lifecycle, running pre-stop hooks, teardown functions, the -stop method, and post-stop hooks. It also untracks the component.n kill: Forcefully stops a component, setting a dynamic flag *kill* to true before calling stop.n info: Retrieves information about the component.n health: Returns the health status of the component (e.g., :ok, :errored).n remote?: Checks if the component interacts with remote resources.n all-props, get-prop, set-prop: Functions for inspecting and manipulating component properties defined via the -props method.n with (macro): A convenient macro for ensuring a component is started before a block of code executes and stopped afterwards, similar to with-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:

  • ITrack Protocol: Components can implement this protocol to define a track-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 stores TrackEntry records for all tracked components.n TrackEntry: 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.n trackable?: Checks if an object implements the ITrack protocol.n track: Registers a component in the global registry. It assigns a unique ID (track/id) to the component if it doesn't have one.n tracked?: Checks if a component instance is currently being tracked.n untrack: Removes a component instance from the global registry.n untrack-all: Clears all or a subset of tracked components from the registry.n tracked: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.n tracked:all: Returns the entire *registry* or a sub-section of it.n tracked: Applies a specified action (function) to all components found at a given path in the registry.n tracked:count: Returns the count of tracked components at a given path.n tracked:locate: Finds tracked components that match specific metadata.n tracked:list: Returns a list of tracked components, optionally applying an action to each.n tracked:last: Retrieves the n most 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