In your project, require:
(require '[piotr-yuxuan.closeable-map :as closeable-map :refer [close-with with-tag]])
Define an application that can be started, and closed.
(defn start
"Return a map describing a running application, and which values may
be closed."
[config]
(closeable-map/closeable-map
{;; Kafka producers/consumers are `java.io.Closeable`.
:producer (kafka-producer config)
:consumer (kafka-consumer config)}))
You can start/stop the app in the repl with:
(comment
(def config (load-config))
(def system (start config))
;; Stop/close all processes/resources with:
(.close system)
)
It can be used in conjunction with with-open
in test file to create
well-contained, independent tests:
(with-open [{:keys [consumer] :as app} (start config)]
(testing "unit test with isolated, repeatable context"
(is (= :yay/🚀 (some-business/function consumer)))))
(defn start
"Return a map describing a running application, and which values may
be closed."
[config]
(closeable-map/closeable-map
{;; Kafka producers/consumers are `java.io.Closeable`.
:producer (kafka-producer config)
:consumer (kafka-consumer config)
;; File streams are `java.io.Closeable` too:
:logfile (io/output-stream (io/file "/tmp/log.txt"))
;; Closeable maps can be nested. Nested maps will be closed before the outer map.
:backend/api {:response-executor (close-with (memfn ^ExecutorService .shutdown)
(flow/utilization-executor (:executor config)))
:connection-pool (close-with (memfn ^IPool .shutdown)
(http/connection-pool {:pool-opts config}))
;; These functions receive their map as argument.
::closeable-map/before-close (fn [m] (backend/give-up-leadership config m))
::closeable-map/after-close (fn [m] (backend/close-connection config m))}
;; Any exception when closing this nested map will be swallowed
;; and not bubbled up.
:db ^::closeable-map/swallow {;; Connection are `java.io.Closeable`, too:
:db-conn (jdbc/get-connection (:db config))}
;; Some libs return a zero-argument function which when called
;; stops the server, like:
:server (with-tag ::closeable-map/fn (http/start-server (api config) (:server config)))
;; Gotcha: Clojure meta data can only be attached on 'concrete'
;; objects; they are lost on literal forms (see above).
:forensic ^::closeable-map/fn #(metrics/report-death!)
::closeable-map/ex-handler
(fn [ex]
;; Will be called for all exceptions thrown when closing this
;; map and nested items.
(println (ex-message ex)))}))
When (.close system)
is executed, it will:
Recursively close all instances of java.io.Closeable
and
java.lang.AutoCloseable
;
Recursively call all stop zero-argument functions tagged with
^::closeable-map/fn
;
Skip all nested Closeable
under a ^::closeable-map/ignore
;
Silently swallow any exception with ^::closeable-map/swallow
;
Exceptions to optional ::closeable-map/ex-handler
in key or
metadata;
If keys (or metadata) ::closeable-map/before-close
or
::closeable-map/after-close
are present, they will be assumed as
a function which takes one argument (the map itself) and used run
additional closing logic:
(closeable-map
{;; This function will be executed before the auto close.
::closeable-map/before-close (fn [this-map] (flush!))
;; Kafka producers/consumers are java.io.Closeable
:producer (kafka-producer config)
:consumer (kafka-consumer config)
;; This function will be executed after the auto close.
::closeable-map/after-close (fn [this-map] (garbage/collect!))
}
)
Some classes do not implement java.lang.AutoCloseable
but present
some similar method. For example instances of
java.util.concurrent.ExecutorService
can't be closed but they can be
.shutdown
:
{:response-executor (close-with (memfn ^ExecutorService .shutdown)
(flow/utilization-executor (:executor config)))
:connection-pool (close-with (memfn ^IPool .shutdown)
(http/connection-pool {:pool-opts config}))}
You may also extend this library by giving new dispatch values to
multimethod piotr-yuxuan.closeable-map/close!
. Once evaluated,
this will work accross all your code. The multimethod is dispatched on
the concrete class of its argument:
(import '(java.util.concurrent ExecutorService))
(defmethod closeable-map/close! ExecutorService
[x]
(.shutdown ^ExecutorService x))
(import '(io.aleph.dirigiste IPool))
(defmethod closeable-map/close! IPool
[x]
(.shutdown ^IPool x))
In your project, require: ``` clojure (require '[piotr-yuxuan.closeable-map :as closeable-map :refer [close-with with-tag]]) ``` Define an application that can be started, and closed. ``` (defn start "Return a map describing a running application, and which values may be closed." [config] (closeable-map/closeable-map {;; Kafka producers/consumers are `java.io.Closeable`. :producer (kafka-producer config) :consumer (kafka-consumer config)})) ``` You can start/stop the app in the repl with: ``` clojure (comment (def config (load-config)) (def system (start config)) ;; Stop/close all processes/resources with: (.close system) ) ``` It can be used in conjunction with `with-open` in test file to create well-contained, independent tests: ``` clojure (with-open [{:keys [consumer] :as app} (start config)] (testing "unit test with isolated, repeatable context" (is (= :yay/🚀 (some-business/function consumer))))) ``` ## More details ``` clojure (defn start "Return a map describing a running application, and which values may be closed." [config] (closeable-map/closeable-map {;; Kafka producers/consumers are `java.io.Closeable`. :producer (kafka-producer config) :consumer (kafka-consumer config) ;; File streams are `java.io.Closeable` too: :logfile (io/output-stream (io/file "/tmp/log.txt")) ;; Closeable maps can be nested. Nested maps will be closed before the outer map. :backend/api {:response-executor (close-with (memfn ^ExecutorService .shutdown) (flow/utilization-executor (:executor config))) :connection-pool (close-with (memfn ^IPool .shutdown) (http/connection-pool {:pool-opts config})) ;; These functions receive their map as argument. ::closeable-map/before-close (fn [m] (backend/give-up-leadership config m)) ::closeable-map/after-close (fn [m] (backend/close-connection config m))} ;; Any exception when closing this nested map will be swallowed ;; and not bubbled up. :db ^::closeable-map/swallow {;; Connection are `java.io.Closeable`, too: :db-conn (jdbc/get-connection (:db config))} ;; Some libs return a zero-argument function which when called ;; stops the server, like: :server (with-tag ::closeable-map/fn (http/start-server (api config) (:server config))) ;; Gotcha: Clojure meta data can only be attached on 'concrete' ;; objects; they are lost on literal forms (see above). :forensic ^::closeable-map/fn #(metrics/report-death!) ::closeable-map/ex-handler (fn [ex] ;; Will be called for all exceptions thrown when closing this ;; map and nested items. (println (ex-message ex)))})) ``` When `(.close system)` is executed, it will: - Recursively close all instances of `java.io.Closeable` and `java.lang.AutoCloseable`; - Recursively call all stop zero-argument functions tagged with `^::closeable-map/fn`; - Skip all nested `Closeable` under a `^::closeable-map/ignore`; - Silently swallow any exception with `^::closeable-map/swallow`; - Exceptions to optional `::closeable-map/ex-handler` in key or metadata; - If keys (or metadata) `::closeable-map/before-close` or `::closeable-map/after-close` are present, they will be assumed as a function which takes one argument (the map itself) and used run additional closing logic: ``` clojure (closeable-map {;; This function will be executed before the auto close. ::closeable-map/before-close (fn [this-map] (flush!)) ;; Kafka producers/consumers are java.io.Closeable :producer (kafka-producer config) :consumer (kafka-consumer config) ;; This function will be executed after the auto close. ::closeable-map/after-close (fn [this-map] (garbage/collect!)) } ) ``` Some classes do not implement `java.lang.AutoCloseable` but present some similar method. For example instances of `java.util.concurrent.ExecutorService` can't be closed but they can be `.shutdown`: ``` clojure {:response-executor (close-with (memfn ^ExecutorService .shutdown) (flow/utilization-executor (:executor config))) :connection-pool (close-with (memfn ^IPool .shutdown) (http/connection-pool {:pool-opts config}))} ``` You may also extend this library by giving new dispatch values to multimethod [[piotr-yuxuan.closeable-map/close!]]. Once evaluated, this will work accross all your code. The multimethod is dispatched on the concrete class of its argument: ``` clojure (import '(java.util.concurrent ExecutorService)) (defmethod closeable-map/close! ExecutorService [x] (.shutdown ^ExecutorService x)) (import '(io.aleph.dirigiste IPool)) (defmethod closeable-map/close! IPool [x] (.shutdown ^IPool x)) ```
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close