Skip to content

Instantly share code, notes, and snippets.

@aroemers
Last active Jan 15, 2021
Embed
What would you like to do?
Component-lite
(ns app
(:require [lifecycle :refer [combine parallel]]))
(defn components [app-config]
(combine (parallel (users-db/component)
(tasks-db/component))
(rest-api/component app-config)))
(ns core
(:require [lifecycle :refer [start stop combine]]))
(defn -main [& args]
(let [component (combine (logger/component)
(jdbc/component jdbc-config)
(app/components app-config))
system (start component {})]
;; bonus: the empty map above could be filled by another
;; lifecycle library/approach.
...
(stop component system)))
(ns jdbc
(:require [hikari-cp.core :as hikari]
[lifecycle :refer [Lifecycle]]))
(defn component [config]
(reify Lifecycle
(start [_ {:keys [logger]}]
(logger "starting JDBC pool...")
{:jdbc (hikari/make-datasource config)})
(stop [_ {:keys [logger jdbc]}]
(logger "stopping JDBC pool...")
(hikari/close-datasource jdbc))))
(ns lifecycle)
(defprotocol Lifecycle
:extend-via-metadata true
(start [this system]
"Starts the component, using the given system map, returning an
(updated) system map.")
(stop [this system]
"Stops the component, using the given system map."))
(defn wrap-merge-system
"Wraps a Lifecycle such that the input system is merged with the
returned system from the wrapped start implementation."
[lifecycle]
(reify Lifecycle
(start [_ system]
(merge system (start lifecycle system)))
(stop [_ system]
(stop lifecycle system))))
(defn wrap-ex-info
"Wraps a Lifecycle such that the input system is inside an
ExceptionInfo wrapping a thrown exception from the wrapped start
implementation. The input system is available under the
key :input-system in the `ex-data`."
[lifecycle]
(reify Lifecycle
(start [_ system]
(try
(start lifecycle system)
(catch Throwable t
(if (:input-system (ex-data t))
(throw t)
(throw (ex-info "Starting failed" {:input-system system} t))))))
(stop [_ system]
(stop lifecycle system))))
(defn combine
"Combines a sequence of Lifecycles into a single Lifecycle, starting
them in order and stopping them in reverse order.
It wraps the given Lifecycles with the `wrap-merge-system` and
`wrap-ex-info` functions."
[& lifecycles]
(reify Lifecycle
(start [_ system]
(reduce (fn [system' lifecycle]
(let [lifecycle' (-> lifecycle wrap-merge-system wrap-ex-info)]
(start lifecycle' system')))
system lifecycles))
(stop [_ system]
(doseq [lifecycle (reverse lifecycles)]
(stop lifecycle system)))))
(defn parallel
"Combines multiple Lifecycles into a single Lifecycle, starting and
stopping them in parallel. The resulting sub-systems are merged into
one."
[& lifecycles]
(reify Lifecycle
(start [_ system]
(->> (mapv #(future (start % system)) lifecycles)
(map deref)
(apply merge)))
(stop [_ system]
(->> (mapv #(future (stop % system)) lifecycles)
(mapv deref)))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment