2

How do I bind a dynamic variable in compojure? Please see my example below, here the request-id is a unique uuid which is generate for each api request. I would like to be able to access this request-id in subsequent methods for logging etc. I have tried using the binding function, but am still not able to access request-id in some-page/some-method.

handler.clj

(ns some_app.handler
  (:require
    [compojure.api.sweet :refer :all]
    [compojure.route :as route]
    [some_api.some_page :as some-page]))

(def ^:dynamic *request-id*
  nil)

(defn ^:private generate-request-id []
  (str (java.util.UUID/randomUUID)))

(def app
  (binding [*request-id* (generate-request-id)]
    (api
      (context "/api" [] (GET "/some-page" [] (some-page/some-method))))))

some-page.clj

(ns some_app.some_page
(:require
        [clojure.tools.logging :as log]))

(def some-method []
  (log/info {:request-id *request-id*}))
Freid001
  • 2,580
  • 3
  • 29
  • 60

3 Answers3

5

The call to binding here is in the wrong place. The binding should be in effect when the request is processed, not when the app/api is being constructed.

You want to have some middleware to do this:

(defn with-request-id 
  [f]
  (fn [request]
    (binding [*request-id* (generate-request-id)]
      (f request)))

(def app
  (with-request-id
    (api ... ))

See also Ring Concepts

Joost Diepenmaat
  • 17,633
  • 3
  • 44
  • 53
1

In your some_app.some_page namespace, you need to require the namespace where *request-id* is declared. Something like:

(ns some_app.some_page
  (:require
    [clojure.tools.logging :as log]
    [some_app.handler :as hndlr))

Then you can refer to the *request-id* like:

(def some-method []
  (log/info {:request-id hndlr/*request-id*}))
clartaq
  • 5,320
  • 3
  • 39
  • 49
  • I don't think this will work because it will create a cyclic dependency. – Freid001 Jan 03 '18 at 10:47
  • Freid001 -- you are quite right. I didn't read closely enough. But, you still need to provide a way for the `some_app.some_page` namespace to reference the variable. – clartaq Jan 03 '18 at 14:24
  • Yep, so I was able to resolved this by storing the variable in a common shared namespace which both some_page and handler could access. – Freid001 Jan 03 '18 at 16:46
1

dynamic binding is a fine approach, and over time it can grow unweildly as a codebase grows, at least compared to storing data about the request in the request it's self.

The ring model encourages storing things about the request directly in the request as data, rather than in metadata or environment things like bound variables.

(defn with-request-id 
  [f]
  (fn [request]
      (f (assoc request :request-id (generate-request-id)))

then you don't need to worry about where thread bindings are preserved or other such concerns.

Arthur Ulfeldt
  • 90,827
  • 27
  • 201
  • 284