Skip to content

Instantly share code, notes, and snippets.

@dadair-ca
Created December 10, 2019 00:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dadair-ca/8779f44010c999cb7c975ea3b9c25eff to your computer and use it in GitHub Desktop.
Save dadair-ca/8779f44010c999cb7c975ea3b9c25eff to your computer and use it in GitHub Desktop.
;; Let's say we have a function `foo`, that we want to decorate with logging:
(defn foo [x]
(inc x))
;; First, let's change the name to suggest an impl:
(defn foo* [x]
(inc x))
;; we could call this function as: `(foo* 1)`
;; Then, let's apply some HOF to augment/decorate the function:
(defn foo [x]
(log :debug (str "x is" x))
(let [result (foo* x)]
(log :debug (str "result is" result))
result))
;; Or, with the middleware pattern:
(defn wrap-logging [foo]
(fn [x] ;; need to match `foo`'s interface
(log :debug (str "input: " x))
(doto (foo x)
(->> (str "output: ") (log :debug)))))
(def foo
(-> foo*
;; can define more middleware/decorators here as needed.
;; this makes the middleware/decorator pattern more extensible (and maintainable) than "intimate wrapping", as in L15.
wrap-logging))
@dadair-ca
Copy link
Author

This decorator pattern could be used also for restricting access to functions based off identity/permissions.

Given some way of accessing an IdentityContext (e.g, a pulled dynamic var), once could write middleware such that:

(defn require-permissions [required-permissions foo]
  (fn [x]
    (let [{current-identity :identity/current} (identity/get-context)]
      (if (identity/permitted? required-permissions current-identity)
        (foo x)
        (throw (identity/insufficient-permissions-exception required-permissions current-identity))))))

(def foo
  (-> foo*
    wrap-logging
    (partial require-permissions #{permissions/x permissions/y})))

@dadair-ca
Copy link
Author

The nice thing about the above Identity example, is that it is all functionality-agnostic. That all could be defined inside the Identity module, and reused across the application.

For the IdentityContext functionality, the immediate solution that comes to mind is rebinding a dynamic var.

(defn get-context []
  *context*)

(defmacro with-context [new-context & forms]
  {:pre [(types/is? ::context new-context)]}
  `(binding [*context* ~new-context]
    ~@forms))  

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment