Skip to content

Instantly share code, notes, and snippets.

@oporkka
Last active March 8, 2018 12:43
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 oporkka/d36d3f4989c37ed92e69883368ed9fe3 to your computer and use it in GitHub Desktop.
Save oporkka/d36d3f4989c37ed92e69883368ed9fe3 to your computer and use it in GitHub Desktop.
An example of having a request-scoped stuartsierra/component with Ring middleware

Asking for comments on this approach

The idea here is to simplify how we are passing the request the metadata type header "X-Flow-Id" to web service client methods which in turn propagate it to the other microservices that they are calling for flow tracking purposes.

Instead of passing e.g. an extra request-ctx parameter to the web service client method:

  (defn entities [component entity-id request-ctx] ...)

we can include the flow-id as a stuartsierra/component dependency

  (defn entities [{:keys [flow-id] :as component} entity-id] ...)

This way all the business logic between the API controller method and the web service client does not have to directly know about the request-ctx or flow-id, and we do not need to include there extra parameters for just passing the metadata kind X-Flow-Id header to the dependency.

  (defn business-logic [{:keys [client-component]} entity-id request-ctx]
    ...
    (client/entities client-component entity-id request-ctx)
    ...
  )

becomes

  (defn business-logic [{:keys [client-component]} entity-id]
    ...
    (client/entities client-component entity-id)
    ...
  )

The dependencies is handled when calling component/map->SystemMap, and the business logic and API controller code stays simpler, and can concentrating to the actual business logic.

The clear downside here is that getting the flow-id header using (flow-id/to-header flow-id) in the web-service-client/entity method, does not make very clear that we are dealing with dynamic binding, which can cause problems in multithreaded environments.

(ns flow-id)
(def ^:dynamic ^:private *flow-id* "")
(def flow-id #'*flow-id*)
(defn to-header [flow-id-component]
(if (var? flow-id-component)
{:x-flow-id (var-get flow-id-component)}
{}))
(defn middleware
"Ring middleware that binds x-flow-id header value from
the headers to be used for flow-id value and injected as a dependency
where needed.
Requires that headers have the key \"x-flow-id\" in order to get the
value. In case there is another middleware that generates this value,
that middleware has to be processed before this one."
[handler]
(fn [{:keys [headers] :as request}]
(let [flow-id (get headers "x-flow-id")]
(binding [*flow-id* flow-id]
(handler request)))))
(ns web-service-client
(:require [clojure.tools.logging :as log]
[clj-http/client :as http]))
(def component {})
(defn entity
[{:keys [host flow-id] :as component} entity-id]
(let [url (str host "/entities/" entity-id)]
(http/request {:url url
:method :get
:headers (flow-id/to-header flow-id)})))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment