Fork me on GitHub

@dharrigan if you wrap, you will have to consider how you will compose your wrapped functions - which will no doubt lead you to the reader monad, which is fine, but you'll need a bunch more things to make it palatable


my currently favoured approaches are either a reader or to pass a map with namespaced keywords so


(defn foo [{db ::db/conn} sql] (select db sql ...))


Why would you pass a map there?


future flexibility @dominicm - so that db-related fns can call on other fns which need other deps... there are obvs other ioc type solutions to this problem, but open-maps are very simple and work quite nicely


That's another approach. I've really enjoyed listening to people give their thoughts on this


all very valuable.


Thank you everyone 🙂


Coming from an OO world, I'm trying to map how to test and do dependencies (and mocking!) in the functional world


so all very useful, thank you! 🙂


@mccraigmccraig I wouldn't do it like that though. I would only do it if I'd defined that map as being some concept. Otherwise you have an ambiguously defined map where the caller is just as free to do {::db/conn ...} as some-map-with-other-deps-in. You'll never realistically be able to introduce a new key to that map unless it's optional (in which case you can probably use a vararg).


that map is a defined concept... a map with at least a ::db/conn key with a value of a particular type which can be checked by a schema or spec - and yes, new keys would be optional, or more likely the map would be open to any new keys (which is how spec behaves normally isn't it - not sure, never really used it)


I'm wary of your suggestion because it leads to the creation of a system or deps or something which has every key in the whole world in.


it does - i don't think i particularly mind that, since namespaced keywords give a decent way of avoiding clashes


Clashes aren't the problem, the problem is:

(defn send-email [deps email]
  (http/post (:client deps) {:params {:api (get-api deps email)}}))
What is deps? What needs to be in it? What isn't in it? What do you have access to?


The answer is that you cant know, you have to read all of the code and keep a running tally as you go.


well, you know what a particular concern needs to be present and enforce that. you don't really care about anything else


I've seen a number of bugs arising from assuming that, e.g. deps contains ::foo/bar already in all places that call it, because the path they're testing right now (sending emails from the accounts page) works.


Let's assume that spec isn't in use "enforcing" this (as spec isn't very good for this kind of problem)


ah, yeah, if you create the deps maps "manually" then that could be a problem - our deps maps are generally created by an ioc thing, so that kind of bug never happens, except while changing the ioc spec during dev


@mccraigmccraig If you're using ioc then you have a name for that map 🙂 It's the "email-deps" or "foo-deps". It can be opaque except for to the system and for some internal definition. I don't think it should be treated like a map though.


in our system there's quite often been a progression with context objects from something quite opaque, like a java db connection object, to a clojure record which implements a protocol and can have other keys assoc'd to it - and while the core functions around a particular context object (e.g. db access) are usually driven through a protocol it's certainly been convenient to access additional things via get, as if the context object was a map


I’m currently looking at improving the performance of a lib I published (first proper open source lib). It’s a pretty basic port of some existing code in js, just trying to currently work out how to make it faster. It does delaunay triangulation. I’ve got a reddit thread going if anyone is interested =)…


For something like ::db/conn, what are the benefits of using a namespaced keyword? Considering you'll probably be using the db connection from multiple namespaces. What is the convention here? Do you declare some namespace to be the owner of the database connection. or is it better to use something like :db/conn (iirc Datomic uses the latter), cc: @mccraigmccraig

Wes Hall16:12:10

@dharrigan I wrote a longer post on your question earlier, then I remembered I was on holiday and couldn’t be arsed to get into an argument about it. Though now I’m relaxing and potentially slightly more arsed... General thrust was, I think there is merit in your “wrapped” example. It’s nice to give downstream components simple functions that they can pass what they have and get what they need without having to pass connection objects around. Your wrap example makes for clean separation of the “setup” part (which happens once) and “usage” part, which happens often. Partial is fine for this, but writing it explicitly does have the advantage of making this separation more explicit. Your downstream components don’t care that they are accessing a rdbms. No need to make them care.

👍 4

Thanks @wesley.hall for being "arsed" 🙂


Everyone has contributed a lot to the discussion


I have to ponder now on everything, i.e., hammock time! 🙂


ha, i don't know @cfeckardt... I've done both... but if you declare some ns to be the owner then you can't put much in it otherwise your impl namespaces can't require it without circular deps, so you probably end up with the .core ns pattern


i've mostly been using

(defn foo
  ([x y z] (foo (db/conn) x y z)
  ([db x y z] ...))
for this kind of situation. We try to write the core of our applications as functional as possible, with the shell (as in main method) calling out to each function with an external dependency individually and mapping/reducing the results, so I rarely need to inject more than one dependency into my fns.


so many interesting approaches!


question, do you hold on to the connection, within your db namespace, inside an atom?


or do you recreate each time?


we use mount-lite, but essentially yes, in a def


reconnecting is too expensive for our use case


kewl, I do similar, I use hikaricp (via next jdbc) to get the connection, then I store that in my db namespace


we've been toying with the idea of maintaining separate connections for reads/writes but not gotten around to sorting that out yet


we hold on to a pool of connections inside an agent (rather than an atom, so that we can guarantee creating just a single instance across all threads)


indeed swap! may execute the fn more than once to retry, and creating a pool is not idempotent


yep, this actually happened in an early version of our app-context management stuff, before we switched from an atom to an agent


@dharrigan Do you store the Connection or the DataSource? I'd be wary of trying to reuse a single connection everywhere -- it's safer to reuse the pooled datasource in my experience.


If you use the datasource then it'll reap bad connections, and recover from network glitches etc.




(you said connection above, which is why I asked)


Ah, sorry, slip of t'tongue


(defn connection-pool-start
  (let [datasource (connection/->pool HikariDataSource config)]
    (reset! ds datasource)