Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

clojure: best approach for singleton-like data connection

Tags:

clojure

I'm creating a connection to elasticsearch (but substitute any other data source of your liking here), and it will be based on environment or config file parameters at runtime. It looks like this:

(defn create-conn
  "Connect to the given uri. This is a persistent conn managed by clj-http (apache)."
  ([uri]
   ( ;;; create a persistent connection using clj-http / elastic
   ...)
  ([]
   (create-conn (or (System/getenv "ES_URL")
                    (cfg/get-url-from-config-file)
                    "http://localhost:9200"))))

Because the connection will not change during the lifetime of the server, I will only need to run this function once and cache the result. There are a few ways to do this:

1 - memoize it -- while this works, it doesn't feel like the right approach since I'd only be caching one thing

2 - use a state manager like Component or mount; since I'm not really managing state, just setting and forgetting, and only using it for this one thing, it feels a little overkill. For example, how is the following superior to #3 below?

; mount version -- good, but how is this better than #3 below?
(defstate conn :start (getconn))
(mount/start #'elastic/conn)   ;; somewhere else, must start mount

3 - def it. Even though running create-conn does not actually perform any network activity, I'd rather not run it when the file loads, which is what would happen if I just did a regular def on it, so I'd have to do something like the following... note that the getconn function is for convenience, so that I don't have to deref conn directly:

(def conn (delay (create-conn)))
(defn getconn [] @conn)

4 - use an atom -- straightforward, but this is not a var which needs to change, and it introduces state where it is not really necessary:

(def conn (atom nil))
(def getconn []
  (if-not @conn (reset! conn (create-conn)))
  @conn)

5 - [insert your idea here]

Of the above, I prefer 3, since it is using immutable data, even though the delay is a little clunky. What is your preference, whether it's from the above list, or your own solution?

like image 832
Josh Avatar asked Sep 19 '16 17:09

Josh


2 Answers

Mount or component are built exactly for this - controlling state that is mostly fixed (and connections sometimes need to be renewed). Mount is the simplest of the two libs and would allow you to the define the connection once and 'require' it wherever you need it.

like image 164
Arnon Rotem-Gal-Oz Avatar answered Oct 09 '22 05:10

Arnon Rotem-Gal-Oz


Even if conn doesn't change after start, you probably want to run the same repository functions against different databases, if you value integration tests. After you load the project in the REPL and start serving request using some database, you may want to write a few integration tests running against a different database. Just an atom is not good enough, if you develop tests and serve requests in parallel. The simplest and I think most idiomatic way is to def the connection and pass it as an argument to each repository function (passing another value in the tests). Something with a dynamic variable and an atom can result in less code but turn out more complex.

like image 23
Assen Kolov Avatar answered Oct 09 '22 05:10

Assen Kolov