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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With