Skip to content

System

BigConfig System is a library for implementing system lifecycles as programmable workflows rather than static dependency graphs. You should be familiar with libraries like Integrant .

  1. Add BigConfig as a dependency to your project
    Terminal window
    neil add dep io.github.amiorin/big-config
  2. Require the system library, it is just 6 functions so far
    (ns systems
    (:require
    [babashka.process :as p]
    [big-config.core :refer [->workflow]]
    [big-config.system :as system :refer [stop stop! add-stop-fn re-process env destroy!]]))
  3. Trivial background process in place of a real database for example
    (let [kill-timeout 150
    shutdown-timeout 100
    clj-timeout 3000]
    ;; Define a managed background process with a specific lifecycle
    ;; Start the process in the background and block the main thread
    ;; until the regex is found or the timeout triggers.
    (defn background-process [opts]
    (let [re-opts (into {} [[:cmd (format "clj -X big-config.system/main :shutdown-timeout %s" shutdown-timeout)]
    [:regex #"token"]
    [:timeout clj-timeout]
    [:key ::proc]])
    opts (re-process re-opts opts)]
    (add-stop-fn opts (fn [{:keys [::proc] :as opts}]
    (when proc
    (destroy! proc kill-timeout)))))))
    (defn main [& {:keys [shutdown-timeout]}]
    (assert shutdown-timeout)
    (.addShutdownHook (Runtime/getRuntime)
    (Thread. (fn []
    (println "\n[Shutdown Hook] Cleaning up resources before exit...")
    (Thread/sleep shutdown-timeout))))
    (println "Script is running... (Press Ctrl+C to test)")
    (println "token")
    @(promise))
  4. The system function is just a BigConfig workflow
    ;; Define the system as a stateful, wired workflow
    (def ->system
    (->workflow {:first-step ::start
    :wire-fn (fn [step _]
    (case step
    ::start [background-process ::end]
    ::end [stop]))}))
  5. Start and stop the system differently based on the use case
    ;; sys1 starts and stops the system. It useful during the development of the system itself.
    ;; sys2 starts only. This is useful in all the other cases.
    (into {} [[:sys1 (->system [log-step-fn] {::bc/env :repl})]
    [:sys2 (let [system (atom (->system [log-step-fn]
    {::bc/env :repl ::async true}))]
    (stop! @system)
    @system)]])

In Clojure’s clojure.test framework, use-fixtures is the standard mechanism for managing setup and teardown logic across your test suite. It allows you to wrap your tests with specific functions to prepare a clean environment—such as starting a database connection or binding dynamic variables—and then clean up afterward. You can apply fixtures at two levels: each (running once for every individual test) or once (running a single time for the entire namespace). A fixture is simply a higher-order function that accepts a test function as an argument, executes your setup code, calls the test function, and finally runs your teardown logic.

(defonce system (atom nil))
(defn with-system [f]
(when @system
(system/stop! @system))
(reset! system (components/->system {::bc/env :repl
::components/profile :test
::system/async true}))
(f))
(use-fixtures :each with-system)

Testing with BigConfig System