4

Being new to clojure, I would like some advice on implementing the repository pattern*.

In an OO language, I would create a repository interface, a test and at least one db impl. I would instantiate them during bootstrap, pass the objects around using DI or get them through a service locator. I'm guessing it's done completely different in clojure?

1) What is a good way of grouping the functions in the repo? Protocol, "free" functions in a namespace?

2) Where do I instantiate the repository back-end, i.e. allocate resources like db-connections etc.? Do I instantiate an implementation of the repository protocol and assign it to an atom, or in the case of free functions, redefine them?

*) A repository is a back-end abstraction for persistence and typically supports a CRUD style range of operations.

EDIT: Here is the approach I'm currently using. A protocol to group functions. A test and a "real" record implementing it. Then an atom to register the repo with.

(defprotocol GardenRepo
  "Repository of Gardens. Supports CRUD style operations."
  (list-gardens [repo] "Get a seq of all gardens in the repo.")
  (get-garden [repo id] "Get a specific garden by it's id.")
  ...)                                                                

(let [repo (atom nil)]
  (defn get-garden-repo [] @locator)
  (defn set-garden-repo [new-repo] (reset! repo new-repo)))
4ZM
  • 1,463
  • 1
  • 11
  • 21

1 Answers1

3

1) Group functions by shared subproblems. In our ORM we have a namespace for db interaction, a separate namespace for each target db, a namespace for model construction and query operations, a namespace for field definition, a separate namespace describing each implementation of the field protocol (ie. int, string, text, slug, collection).

2) Use a function that returns all the functions used, each implicitly using resources defined by the passed in config, ie.:

(defn make-repository
  [config]
  (let [db (initialize-db config)
        cleanup #(do-cleanup db)
        store (fn [key val] (store-data key val db))
        retrieve (fn [key] (retrieve-data key db))]
    {:db db ;; this is optional, can be very useful during initial development
     :cleanup cleanup
     :store store
     :retrieve retrieve}))

This could of course create an instance of a record if access of the functions is a performance bottleneck, and the record could implement a protocol if you want to define multiple implementations of the same functionality (different db drivers that may need different setup etc.). The user of the library decides how and where to bind these functions you return, as appropriate for their own design.

An example of how a client could use this repository:

(def repo (make-repository config))

(def cleanup-repo (:cleanup repo))

(def store-to-repo (:store repo))

(def retrieve-from-repo (:retrieve repo))

(defn store-item
  [data]
  (let [processed (process data)
        key (generate-key data)]
    (try (store-to-repo key data)
      (catch Exception e
         (cleanup-repo)))))
noisesmith
  • 20,076
  • 2
  • 41
  • 49
  • There are other possibilities for option 2, of course. I pick this implementation because it localizes state to one place (both in code and in program storage) and it is trivial to make it reentrant. Reentrancy is easier if you do it from the very beginning - the longer you expand on a non-reentrant design, the harder the conversion gets. – noisesmith Oct 21 '13 at 14:47
  • I've done something similar but used records. See my updated question. How do you "register" your repository? Or do you pass it around using only function arguments? – 4ZM Oct 21 '13 at 16:13
  • defn inside let with an atom is not actually especially useful. The function is stuck using the specific atom (so it is not re-entrant). I will update with an example of how to keep things modular. – noisesmith Oct 21 '13 at 21:23
  • 1
    Also, records can impede repl based interactive development, bacause they require a full stop, clean, and restart of the jvm before a a changed definition can be reliably used. And unless you are using the record inside a tight loop, they really don't give much advantage. – noisesmith Oct 21 '13 at 21:36
  • Good points. My attempt to do encapsulation is probably just an old OO habit. – 4ZM Oct 22 '13 at 07:54