As a Clojure training exercise, I migrate the bulbs ( http://bulbflow.com ), the graph-database library that I wrote, from Python to Clojure.
One of the things that I'm still somewhat blurry about is to structure the library in Clojure's idiomatic key.
Bulbs uses dependency injection to support multiple databases. The various database backends are discarded into the user class Client, which implements the interface, and the client is configured at run time.
The Graph object and its various proxy objects contain an instance of a low-level Client object:
You use Bulbs, creating a Graph object for the corresponding database diagram server:
>>> from bulbs.neo4jserver import Graph >>> g = Graph()
And then you can create vertices and edges in the database through proxy objects:
>>> james = g.vertices.create(name="James") >>> julie = g.vertices.create(name="Julie") >>> g.edges.create(james, "knows", julie)
This design simplifies the use of light bulbs from REPL, because all you need to do is import and create an instance of the Graph object (or, possibly, pass it in a custom Config object).
But I'm not sure how to approach this design in Clojure, since the Graph object and its proxies must hold the Client object that is configured at runtime.
What is the Clojure -way for this?
UPDATE: This is what I ended up doing ...
;; bulbs/neo4jserver/client.clj (def ^:dynamic *config* default-config) (defn set-config! [config] (alter-var-root #'*config* (fn [_] (merge default-config config)))) (defn neo4j-client [& [config]] (set-config! config)) (neo4j-client {:root_uri "http://localhost:7474/data/db/"}) (println *config*)
UPDATE 2:
Andrew Cooke noted that using the global var eliminates the possibility of using several independent graphical βinstancesβ in your program, whereas you can in the Python version.
And so I came up with this:
(defn graph [& [config]] (let [config (get-config config)] (fn [func & args] (apply func config args)))) (defn create-vertex [config data] (let [path (build-path vertex-path) params (remove-null-values data)] (rest/post config path params))) (defn gremlin [config script & [params]] (rest/post config gremlin-path {:script script :params params}))
And then you can call various functions as follows:
(def g (graph {:root_uri "http://localhost:7474/data/db/"})) (g create-vertex {:name "James"}) (g gremlin "gv(id)" {:id 178})
Now I have not delved into the macros, and I'm not too sure about the merits of this approach compared to others, so welcome feedback.