Liking cljdoc? Tell your friends :D

piotr-yuxuan.closeable-map

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)))))

More details

(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))
```
raw docstring

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close