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?
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 defrecord
s to implement some of
them