Dealing with database reads in Clojure

224 views Asked by At

I am trying to 'purify' some of my Clojure functions. I would like to get to a point where all my side-effecting code is explicitly declared in one function. It's easy to get some data at the start and to write it to a db at the end and just have a pure function transforming in between. However, the usual situation is that the transformation function requires another DB read somewhere in the middle of the logic:

(defn transform-users
      [users]
      (let [ids (map :id users)
            profiles (db/read :profiles ids)]
       (profiles->something profiles)))

(->> (db/read :users)
     (transform-users)
     (db/write :something)

Obviously this is a very simple example but the point is, how do I get the side-effecting db/read function out of there, how can I make transform-users pure (and as a benefit, easily testable)?

2

There are 2 answers

1
xsc On BEST ANSWER

One thing you could do here would be a dependency-injection-like approach of supplying the (potentially) side-effectful function as an optional parameter, e.g.:

(defn transform-users
  [users & {:keys [ids->profiles]
            :or {ids->profiles #(db/read :profiles %)}]
  (let [ids (map :id users)
        profiles (ids->profiles ids)]
    (profiles->something profiles)))

This should be easily testable since you can mock the injected functions without a lot of effort. And as a bonus, by supplying the default value, you're documenting what you're expecting and making the function convenient to call.

1
Alister Lee On

Why couple the reading of the profiles with transforming profiles?

  (->> (db/read :users)
       (map :id)
       (db/read :profiles)
       (profile->something)
       (db/write :something)

(This also exposes the fact that you are doing two round trips to the db. Where is db/read :user-profiles ?)

  (->> (db/read :user-profiles)
       (profile->something)
       (db/write :something)

or perhaps:

  (->> (read-profiles-from-users)
       (profile->something)
       (db/write :something)