System Architecture

Data-driven config, multimethods (popular with Kit/Duct) Records implementing Lifecycle (start/stop), explicit dependencies

Global states (defstate) managed by the namespace dependency graph Data-driven, newer

Architecture patterns: a functional core with an imperative shell, data-oriented design, dependency injection via the system map, and configuration with Aero.

;; Integrant: describe the system as data, implement init/halt for each component
(require '[integrant.core :as ig])

(def config
  {:db/pool {:url "jdbc:sqlite:app.db"}
   :app/handler {:db (ig/ref :db/pool)}
   :http/server {:port 3000 :handler (ig/ref :app/handler)}})

(defmethod ig/init-key :db/pool [_ {:keys [url]}]
  (next.jdbc/get-datasource url))

(defmethod ig/init-key :app/handler [_ {:keys [db]}]
  (fn [req] {:status 200 :body (str "DB ready: " (some? db))}))

(defmethod ig/init-key :http/server [_ {:keys [port handler]}]
  (ring.adapter.jetty/run-jetty handler {:port port :join? false}))

(defmethod ig/halt-key! :http/server [_ server]
  (.stop server))

(def system (ig/init config))               ; starts components in dependency order
;; (ig/halt! system)                        ; stops them in reverse order
;; integrant-repl: (go) (reset) (halt) for fast REPL restarts