Liking cljdoc? Tell your friends :D

piotr-yuxuan.closeable-map

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

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

× close