When building REST resources that need to generate hyperlinks to one another (HATEOAS), you may encounter circular dependencies where Resource A needs to know the path for Resource B, and vice versa.
To solve this, use a promise to manage a handler map. This map contains entries for each handler keyed by keywords. By passing a promise into the handler constructors, you can defer the dereferencing of the map until the handlers are actually invoked. This ensures that the make-handlers function can deliver the completed map before the handlers are ever called, preventing the promise from escaping the construction phase prematurely.
Key requirements:
- The promise must be delivered (realized) before calling
path-for. - Use
assert (realized? handlers) within your handlers to ensure the dependency is ready. - Use
path-for with the routes object and the keyword from the handler map to generate paths.
;; 1. Define resources that accept a promise for handlers
(defresource contacts [database handlers]
:handle-created (fn [{{:routes routes :request} :request :id id}]
(assert (realized? handlers))
(ring-response
{:headers {"Location" (path-for routes (:contacts @handlers)) :id id}})))
(defresource contact [handlers]
:handle-ok (fn [{{{id :id} :route-params :routes routes :routes} :request}]
(assert (realized? handlers))
(html [:a {:href (path-for routes (:contacts @handlers))} "Index"])))
;; 2. Use make-handlers to encapsulate the promise delivery
(defn make-handlers [database]
(let [p (promise)]
;; Deliver the promise so it doesn't escape this function.
@(deliver p {:contacts (contacts database p)
:contact (contact p)})))
;; 3. Compose the application
(-> database make-handlers make-routes)