In your project, require:
(require '[piotr-yuxuan.closeable-map :as closeable-map :refer [with-tag]])
Then you can define an application that can be started, and closed.
(defn start
"Return a running context with values that can be closed."
[config]
(closeable-map/closeable-map
{;; Kafka producers/consumers are `java.io.Closeable`.
:producer (kafka-producer config)
:consumer (kafka-consumer config)
;; Closeable maps can be nested.
:backend/api {:response-executor (flow/utilization-executor (:executor config))
:connection-pool (http/connection-pool {:pool-opts config})
;; File streams are `java.io.Closeable` too:
:logfile (io/output-stream (io/file "/tmp/log.txt"))
;; This will be called as final closing step for
;; this nested map backend/api. See also
;; `::closeable-map/before-close` which is called
;; before closing a map.
::closeable-map/after-close
(fn [m]
;; Some classes have similar semantic, but do not
;; implement `java.io.Closeable`. We can handle
;; them anyway.
(.shutdown ^ExecutorService (:response-executor m))
(.shutdown ^IPool (:connection-pool 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)))}))
Then 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)
)
You can use it in conjunction with with-open
like in test file:
(with-open [system (start config)]
(testing "unit test with isolated, repeatable context"
(is (= :yay/🚀 (some-business/function context)))))
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!))
}
)
You can easily extend this library by giving new dispatch values
to multimethod piotr-yuxuan.closeable-map/close!
. It 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 [with-tag]]) ``` Then you can define an application that can be started, and closed. ``` clojure (defn start "Return a running context with values that can be closed." [config] (closeable-map/closeable-map {;; Kafka producers/consumers are `java.io.Closeable`. :producer (kafka-producer config) :consumer (kafka-consumer config) ;; Closeable maps can be nested. :backend/api {:response-executor (flow/utilization-executor (:executor config)) :connection-pool (http/connection-pool {:pool-opts config}) ;; File streams are `java.io.Closeable` too: :logfile (io/output-stream (io/file "/tmp/log.txt")) ;; This will be called as final closing step for ;; this nested map backend/api. See also ;; `::closeable-map/before-close` which is called ;; before closing a map. ::closeable-map/after-close (fn [m] ;; Some classes have similar semantic, but do not ;; implement `java.io.Closeable`. We can handle ;; them anyway. (.shutdown ^ExecutorService (:response-executor m)) (.shutdown ^IPool (:connection-pool 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)))})) ``` Then 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) ) ``` You can use it in conjunction with `with-open` like in test file: ``` clojure (with-open [system (start config)] (testing "unit test with isolated, repeatable context" (is (= :yay/🚀 (some-business/function context))))) ``` 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!)) } ) ``` - You can easily extend this library by giving new dispatch values to multimethod [[piotr-yuxuan.closeable-map/close!]]. It 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