2
votes

I'd like to hide the details of my persistence layer behind some sort of interface. In Java I would just create an interface and choose the correct implementation in some sort of bootup function. I'm still struggling on how to do that in Clojure. I don't necessarily need any type-safety here, I trust in my unit tests to find any issues there. The best thing I could come up with was to create a map containing anonymous functions with specific keys, like so:

(def crux-db {
               :get-by-id (fn [id]     (get-obj...))
               :save      (fn [id obj] (store-obj...))
             })
(def fs-db   {
               :get-by-id (fn [id]     (get-obj...))
               :save      (fn [id obj] (store-obj...))
             })

If I'm not missing something, this would allow me to replace the database implementation by def-ing (def db crux-db) or (def db fs-db), as long as all the functions exist in all implementation maps. Somehow I feel like this is not the clojure way but I can't put my finger on it. Is there another way to do this?

2
Have you looked into protocols?A. Webb

2 Answers

3
votes

Protocols are a way to do that. They let you define what functions should be there. And you can later implement them for different things with e.g. a defrecord.

A protocol is a named set of named methods and their signatures, defined using defprotocol:

(defprotocol AProtocol
  "A doc string for AProtocol abstraction"
  (bar [a b] "bar docs")
  (baz [a] [a b] [a b c] "baz docs"))
  • No implementations are provided
  • Docs can be specified for the protocol and the functions
  • The above yields a set of polymorphic functions and a protocol object
    • all are namespace-qualified by the namespace enclosing the definition
  • The resulting functions dispatch on the type of their first argument, and thus must have at least one argument
  • defprotocol is dynamic, and does not require AOT compilation
  • defprotocol will automatically generate a corresponding interface, with the same name as the protocol, e.g. given a protocol my.ns/Protocol, an interface my.ns.Protocol. The interface will have methods corresponding to the protocol functions, and the protocol will automatically work with instances of the interface.

Since you mentioned crux in your code, you can have a peek at how they use it here and then using defrecords to implement some of them

2
votes

There are several ways to achieve this. One way would be to use protocols. The other way would be to just use higher-order functions, where you would "inject" the specific function and expose it like so:

(defn get-by-id-wrapper [implementation]
  (fn [id]
     (implementation id)
     ...))

(defn cruxdb-get-by-id [id]
     ...)

(def get-by-id (get-by-id-wrapper cruxdb-get-by-id))

Also worth mentioning here are libraries like component or integrant which are used to manage the lifecylce of state.