(ns my-ns)
(def ^:dynamic *foo* "hello")
(defn print-foo []
(println *foo*))
(ns your-ns
(:require [my-ns]))
(binding [my-ns/*foo* "world"]
(my-ns/print-foo))
;; world
(my-ns/print-foo)
;; hello
Hiding complexity behind simple interfaces
Tonight's Example: Database Caching Layer
Requirements for a safe to use interface
With a tree!
Functional and Data Dependencies (as code)
(ns app.user
(:require [app.db :as db]))
(defn read-accounts [user-id]
(db/read-model :accounts {:user-id user-id}))
(defn read-payments [user-id]
(db/read-model :payments {:user-id user-id}))
(defn read-user [user-id]
{:accounts (read-accounts user-id)
:payments (read-payments user-id)})
Functional and Data Dependencies (as a tree)
(ns app.cache
(:require [app.cache.meta :as meta]))
;; will be nil or an atom containing a list
(def ^:dynamic *dependencies* nil)
;; register with parent
(defn log-dependency
"Logs the key as a dependency of the parent
cached context, if there is one."
[dependency-key]
(when *dependencies*
(swap! *dependencies* conj dependency-key)))
;; Save context and return the result
(defn save-dependencies [dependency-key]
(meta/save dependency-key *dependencies))
(ns app.user
(:require [app.db :as db]
[app.cache :as cache]))
(defn read-accounts [user-id]
(db/read-model :accounts {:user-id user-id}))
(ns app.user
(:require [app.db :as db]
[app.cache :as cache]))
(defn read-accounts [user-id]
;; register with parent
(cache/log-dependency :read-accounts)
(db/read-model :accounts {:user-id user-id}))
(ns app.user
(:require [app.db :as db]
[app.cache :as cache]))
(defn read-accounts [user-id]
;; register with parent
(cache/log-dependency :read-accounts)
;; Construct a new context
(binding [cache/*dependencies* (atom '())]
(db/read-model :accounts {:user-id user-id})))
(ns app.user
(:require [app.db :as db]
[app.cache :as cache]))
(defn read-accounts [user-id]
;; register with parent
(cache/log-dependency :read-accounts)
;; Construct a new context
(binding [cache/*dependencies* (atom '())]
(let [accounts ;; Run the function body
(db/read-model
:accounts
{:user-id user-id})]
accounts)))
(ns app.user
(:require [app.db :as db]
[app.cache :as cache]))
(defn read-accounts [user-id]
;; register with parent
(cache/log-dependency :read-accounts)
;; Construct a new context
(binding [cache/*dependencies* (atom '())]
(let [accounts ;; Run the function body
(db/read-model
:accounts
{:user-id user-id})]
;; Save context and
;; return the result
(cache/save-dependencies :read-accounts)
accounts)))
This is fugly :(
(ns app.user
(:require [app.db :as db]
[app.cache :as cache]))
(defn read-accounts [user-id]
(cache/log-dependency :read-accounts)
(binding [cache/*dependencies* (atom '())]
(let [accounts (db/read-model
:accounts
{:user-id user-id})]
(cache/save-dependencies :read-accounts)
accounts)))
(defmacro defcachedfn [fn-name fn-args & body]
(let [fn-key (gen-cache-key fn-name fn-args)]
`(fn [args#]
(if-let [cached# (get-cached ~fn-key)]
cached#
;; register with parent
(log-dependency ~fn-key)
;; Construct a new context
(binding [*dependencies* (atom '())]
;; Run the function body
(let [result# (do ~@body)]
;; Save context and
;; return the result
(save-dependencies ~fn-key)
result#))))))
Without caching
(defn read-accounts [user-id]
(db/read-model :accounts user-id))
With caching
(defcachedfn read-accounts [user-id]
(db/read-model :accounts user-id))
Marshall Brekka - marshall.brekka@avant.com