Skip to content

strojure/fitter

Repository files navigation

fitter

System component management library for Clojure.

Clojars Project

cljdoc badge tests

Similar purpose libraries:

Design goals

  • Describe system component dependencies in single place.
  • Declare system components as easy as new function.
  • Allow ad-hoc and dynamic dependencies defined by components themselves.
  • Fit for systems with any amount of components.
  • Maximum flexibility for any scenario.
  • Help developer to reason about the whole system state.

Terminology

  • instance
    • something useful instantiated :-)
  • component
    • the description how to initialize/destroy instance.
  • registry
    • map of keys and components used in system start/stop operations.
  • system
    • collection of dependent component instances.

Features

  • Start/stop system of components, all registered components or only part of them.
  • Suspend/resume components on system restart.
  • Bind component instances to global vars aka mount (optional).
  • Parallel execution of components during system start/stop (optional).

Basic usage

Component declaration

Minimal component is just a function receiving system “map” of other component instances and returning this component instance. The system “map” supports only ILookup interface, see system-test. All lookups instantiate requested components and form a dependency between components dynamically. (!) All components are tightly coupled together by system key names.

Complete component defines its start, stop and suspend behaviour.

(ns readme.component
  (:require [strojure.fitter.component :as component]))

(def function-component
  "Simple function describes component start behaviour."
  (fn [{:keys [another-component]}]
    (comment "Use" another-component)
    :instance))

(def constant-component
  "Just constant component."
  (constantly true))

(def map-component
  "Component described as hash map with required `::component/start` key."
  {::component/start (constantly :instance)
   ::component/stop! (fn stop! [instance]
                       (comment "Destroy" instance))
   ::component/suspend! (fn suspend! [old-instance old-system]
                          (comment "Suspend" old-instance old-system)
                          (fn resume [new-system]
                            (comment "Resume" old-instance new-system)
                            :instance))})

(def assembled-component
  "Same map as above created using `component/of`."
  (component/of (constantly :instance)
                (fn stop! [instance]
                  (comment "Destroy" instance))
                (fn suspend! [old-instance old-system]
                  (comment "Suspend" old-instance old-system)
                  (fn resume [new-system]
                    (comment "Resume" old-instance new-system)
                    :instance))))

System state

System state is a variable holding instances of the running components. The state is initialized by init and then altered by start! and stop!.

(ns readme.system-state
  (:require [strojure.fitter.system :as system]))

(def ^:private registry
  {::a (constantly ::a)
   ::b (fn [{::keys [a]}] {::b a})})

;; Initialize system state.
(defonce ^:private system!
  (system/init {:registry registry}))

;; Start all system keys.
(system/start! system!)

;; Stop all running keys.
(system/stop! system!)

;; The `start!`, `stop!` and `deref` return the actual system map. 
(let [{::keys [a b]} (system/start! system!)]
  (comment "Work with" a b))
(let [_ (system/start! system!)
      {::keys [a b]} (deref system!)]
  (comment "Work with" a b))

;; Start/stop only specific keys.
(system/start! system! {:filter-keys #{::a}})
(system/stop! system! {:filter-keys #{::a}})

;; Start/stop system incrementally.
(doto system! (system/start! {:filter-keys #{:a}})
              (system/start! {:filter-keys (complement #{:a})}))
(doto system! (system/stop! {:filter-keys (complement #{:a})})
              (system/stop! {:filter-keys #{:a}}))

;; Update registry on start.
(system/start! system! {:registry (assoc registry ::c (constantly ::c))})

;; Suspend suspendable components on stop and resume them on start.
(doto system! (system/stop! {:suspend true})
              (system/start!))

;; Execute components in parallel
(doto (system/init {:parallel true}) (system/start!)
                                     (system/stop!))
(system/start! system! {:parallel true})
(system/stop! system! {:parallel true})

;; Use `with-open` to stop system automatically.
(with-open [s! (system/init {:registry registry})]
  (let [{::keys [a b]} (system/start! s!)]
    (comment "Work with" a b)))

Examples