Liking cljdoc? Tell your friends :D

If you support my open source work, please consider donating Ukrainian Army in fighting with russian agression. We stay for Freedom and peaceful future, let's stop putin together! πŸ‡ΊπŸ‡¦ πŸ™

IBAN: UA843000010000000047330992708
BTC: 357a3So9CbsNfBBgFYACGvxxS6tMaDoa1P
ETH: 0x165CD37b4C644C2921454429E7F9358d18A45e14
USDT (trc20): TEFccmfQ38cZS1DTZVhsxKVDckA8Y6VfCy
Apple/Google Pay: https://uahelp.monobank.ua

coldbrew Clojars Project

Easy to use Clojure wrappers for Caffeine

Usage

(require '[fmnoise.coldbrew :refer [cached defcached]])

The main building block is cached function which accepts function and returns cached version of it. Cache options are passed as meta attached to function. Supported options are:

  • :expire - expiration time (in seconds), uses expireAfterWrite
  • :refresh - refresh time (in seconds), uses refreshAfterWrite
  • :when - function for performing conditional caching (value is cached only if function returns truthy value)

Let's create a cached function with expiration time of 1 hour (3600 sec):

(defn fetch-customer [base-url id]
  (http/get (str base-url "/customers/" id)))

(def cached-customer-fetch
  (cached (with-meta fetch-customer {:expire 3600})))

We can achieve the same result using an anonymous function:

(def cached-customer-fetch
  (cached
    (with-meta
      (fn [base-url id]
        (http/get (str base-url "/customers/" id)))
      {:expire 3600})))

We can also configure that the value should be cached only when it has satisfied a condition:

;; tasks can be only added to worker and capacity can only decrease
;; so it makes sense to cache value when capacity reaches 0
(def worker-tasks-capacity
  (cached
    (with-meta
      (fn [db worker-id date]
        (some-expensive-query db worker-id date))
      {:when zero?})))

There's also a more flexible defcached macro which uses cached under the hood. The main purpose of it is ability to build cache keys from function agruments:

(defcached fetch-customer ;; function name
  [{:keys [base-url timeout]} id] ;; function args
  ^{:expire 3600} ;; cache options passed as meta attached to cache key
  [id] ;; cache key - a vector which will become args for internal caching function
  ;; function body
  (let [result (deref (http/get (str base-url "/customer/" id)) timeout ::timeout)]
    (if (= result ::timeout)
      (throw (ex-info "Request timed out" {:id id}))
      result))

All meta passed to function name is preserved, so we can have private cached functions and add docstrings:

(defcached
  ^{:private true
    :doc "Fetches customer with given id"}
  fetch-customer-impl [{:keys [base-url timeout]} id]
  ^{:expire 3600}
  [id]
  (let [result (deref (http/get (str base-url "/customer/" id)) timeout ::timeout)]
    (if (= result ::timeout)
      (throw (ex-info "Request timed out" {:id id}))
      result)))

Positional docstring as well as pre/post conditions map are also supported:

(defcached fetch-customer-impl
  "Fetches customer data by given id"
  [{:keys [base-url timeout]} id]
  {:pre [(string? base-url) (pos? timeout) (some? id)]}
  ^{:expire 3600}
  [id]
  (let [result (deref (http/get (str base-url "/customer/" id)) timeout ::timeout)]
    (if (= result ::timeout)
      (throw (ex-info "Request timed out" {:id id}))
      result)))

Due to defn-like declaration it's very easy to refactor existing defn to cached function using the defcached macro:

  1. Change defn to defcached
  2. Add cache key vector before function body (names should correspond with function args) with optional metadata for expiration/refreshing
  3. That's it! :tada:

NB: If you later decide to return back to defn and forget to remove cache key, nothing will break.

Disclaimer

Same as consuming cold brew coffee, make sure you don't exceed recommended caffeine amount, as each call to cached creates separate Caffeine Loading Cache instance. Also make sure you understand the risk of memory leaks when caching large objects or collections.

Status

Successful production usage 😎

Credits

Coldbrew icon made by Freepik from Flaticon

License

Copyright Β© 2021 fmnoise

Distributed under the Eclipse Public License 2.0

Can you improve this documentation? These fine people already did:
fmnoise, alex, The Alchemist & Vincent Cantin
Edit on GitHub

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

Γ— close