Skip to content
clojure-donut-system logo

clojure-donut-system

Reference for donut.party/system - component lifecycle and dependency injection library for Clojure. Use when working with component definitions, system management, refs between components, or lifecycle signals (start/stop/suspend/resume). Triggers on donut.system imports, ::ds/start, ::ds/stop, ...

SKILL.md

Full skill instructions

donut.party/​system

Component lifecycle and dependency injection library for Clojure applications. Helps organize applications as systems of components with managed dependencies and lifecycle.

Setup

deps.edn:

party.donut/​system {:mvn/​version "1.0.257"}

Require:

(require '[donut.system :as ds])

See https://clojars.org/party.donut/system for the latest version.

Quick Start

Define components with ::ds/​start and ::ds/​stop handlers:

(def system
  {::ds/​defs
   {:app
    {:server #::ds{:start (fn [{:keys [::ds/​config]}]
                            (start-server (:port config)))
                   :stop  (fn [{:keys [::ds/​instance]}]
                            (stop-server instance))
                   :config {:port 8080}}}}})

;; Start the system
(def running-system (ds/​signal system ::ds/​start))

;; Stop the system
(ds/​signal running-system ::ds/​stop)

Core Concepts

System Map Structure

{::ds/​defs          ; component definitions go here
 ::ds/​instances     ; runtime instances (created by signals)
 ::ds/​signals       ; custom signal definitions (optional)
 ::ds/​plugins       ; plugins to extend functionality (optional)}

Component Definition

Components are maps with signal handlers:

#::ds{:start  (fn [arg] ...)  ; creates instance
      :stop   (fn [arg] ...)  ; cleans up instance
      :config {...}}          ; configuration data

Component Organization

Components are organized into groups (2-level structure):

{::ds/​defs
 {:group-name         ; first level: component group
  {:component-name    ; second level: component
   #::ds{:start ...}}}}

Signal Handler Arguments

Signal handlers receive a map with:

  • ::ds/​instance - component instance (if exists)
  • ::ds/​config - component config with refs resolved
  • ::ds/​system - entire system map
  • ::ds/​component-id - e.g. [:group :component]

Common Patterns

References Between Components

Use ds/​ref to reference other components:

(def system
  {::ds/​defs
   {:services
    {:db #::ds{:start (fn [_] (create-db-pool))}}

    :app
    {:handler #::ds{:start  (fn [{:keys [::ds/​config]}]
                              (make-handler (:db config)))
                    :config {:db (ds/​ref [:services :db])}}}}})

Refs determine startup order - dependencies start first.

Local Refs (within same group)

Use ds/​local-ref to reference components in the same group:

(def HTTPServer
  #::ds{:start  (fn [{:keys [::ds/​config]}]
                  (start-server (:handler config) (:port config)))
        :config {:handler (ds/​local-ref [:handler])
                 :port    (ds/​local-ref [:port])}})

(def system
  {::ds/​defs
   {:http-1 {:server  HTTPServer
             :handler (fn [req] {:status 200 :body "Server 1"})
             :port    8080}

    :http-2 {:server  HTTPServer
             :handler (fn [req] {:status 200 :body "Server 2"})
             :port    9090}}})

System Data (non-component values)

Maps without ::ds/​start are treated as data and can be referenced:

{::ds/​defs
 {:env {:db-url "jdbc:postgresql://localhost/​mydb"
        :port 8080}

  :services
  {:db #::ds{:start  (fn [{:keys [::ds/​config]}]
                       (connect (:url config)))
             :config {:url (ds/​ref [:env :db-url])}}}}}

Named Systems (environment-specific config)

Define systems by environment:

(defmethod ds/​named-system :dev
  [_]
  {::ds/​defs {:env {:port 8080}
              :app {...}}})

(defmethod ds/​named-system :test
  [_]
  {::ds/​defs {:env {:port 9999}
              :app {...}}})

;; Start with overrides
(ds/​start :dev {[:env :port] 3000})

REPL Workflow

Use donut.system.repl for development:

(require '[donut.system.repl :as dsr])

;; Define default REPL system
(defmethod ds/​named-system :donut.system/​repl
  [_]
  (ds/​system :dev))

;; REPL commands
(dsr/​start)   ; start system
(dsr/​stop)    ; stop system
(dsr/​restart) ; stop, reload namespaces, start

Built-in Signals

  • ::ds/​start - create/​start component instances (reverse-topsort order)
  • ::ds/​stop - stop/​cleanup component instances (topsort order)
  • ::ds/​suspend - pause without full teardown
  • ::ds/​resume - resume suspended components

Convenience functions: ds/​start, ds/​stop, ds/​suspend, ds/​resume

Gotchas / Caveats

  1. Component Organization: Components must be direct children of groups. This won't work:

    {::ds/​defs
     {:group {:subgroup {:component ...}}}}  ; TOO NESTED!
    
  2. Refs Must Be Reachable: Refs must be in the data structure, not hidden in functions:

    ;; BAD - ref inside function, not reachable
    #::ds{:start (fn [_] (ds/​ref [:services :db]))}
    
    ;; GOOD - ref in config
    #::ds{:start  (fn [{:keys [::ds/​config]}] (:db config))
          :config {:db (ds/​ref [:services :db])}}
    
  3. Deep Refs: Can reference into component instances:

    (ds/​ref [:group :component :level-1 :level-2])
    
  4. Idempotent Start Handlers: If you signal the same system multiple times, make start handlers idempotent:

    (fn [{::ds/​keys [instance config]}]
      (or instance (create-component config)))
    

Testing

Test System Fixtures

(use-fixtures :each (ds/​system-fixture ::test))

(deftest my-test
  (is (= expected-value
         @(ds/​instance ds/​*system* [:group :component]))))

Mocking Components

Override components in tests:

(ds/​start ::test
  {[:services :external-api] mock-api
   [:services :email] mock-email-sender})

Advanced Features

  • Custom signals with ::ds/​signals
  • Lifecycle hooks: ::ds/​pre-start, ::ds/​post-start, etc.
  • Component selection: (ds/​start system {} #{[:group :component]})
  • Plugins: extend system functionality
  • Validation plugin: malli schemas for config and instances
  • Subsystems: compose systems from other systems

References