Avoiding global state in the Clojure DAO layer

I am trying to implement ideas from http://thinkrelevance.com/blog/2013/06/04/clojure-workflow-reloaded in my codebase.

I have a dao layer where I now need to transfer the database to avoid the global state. One thing that throws me off is the phrase:

Any function that needs one of these components should take it as a parameter. This is not as burdensome as it may seem: each function receives at most one additional argument, providing the "context" in which it operates. This context may be the whole system object, but more often there will be some subset. With the wise use of lexical closures, additional arguments disappear from most codes.

Where should I use closure to avoid passing global state for every call? One example would be to create an init function at the dao level, something like this:

(defprotocol Persistable (collection-name [this])) (def save nil) (defn init [{:keys [db]}] (alter-var-root #'save (fn [_] (fn [obj] (mc/insert-and-return db (collection-name obj) obj WriteConcern/SAFE))))) 

Thus, I can initiate my dao layer from the system / start function as follows:

 (defn start [{:keys [db] :as system}] (let [d (-> db (mc/connect) (mc/get-db "my-test"))] (dao/init d) (assoc system :db d))) 

It works, but it is a bit unpleasant. Is there a better way? If possible, I would like to avoid forcing my dao level clients to transfer the database every time it uses the function.

+6
source share
1 answer

You can use a higher order function to represent your DAO level - this is the essence of functional programming, using functions to represent small and large parts of your system. Thus, you have a higher-order function that takes a connection to the database as a parameter and returns another function that you can use to call various operations, such as saving, deleting, etc. In the database. The following is one such example:

 (defn db-layer [db-connection] (let [db-operations {:save (fn [obj] (save db-connection obj)) :delete (fn [obj] (delete db-connection obj)) :query (fn [query] (query db-connection query))}] (fn [operation & params] (-> (db-operations operation) (apply params))))) 

Using DB level:

 (let [my-db (create-database) db-layer-fn (db-layer my-db)] (db-layer-fn :save "abc") (db-layer-fn :delete "abc")) 

This is just an example of how higher order functions can allow you to create a context for another set of functions. You can take this concept even further by combining it with other Clojure features, such as protocols.

+8
source

Source: https://habr.com/ru/post/946888/


All Articles