Hey Pablo,

I could be wrong, but it seems that the key problem here is the existence of 
the global transaction. If the global transaction didn’t exist then any time 
you failed to pass in a transaction the code would fail: immediately and loudly.

I appreciate what you’re trying to do but it seems like you’re on a path to 
solve the problems caused by one shared, implicit, global variable by creating 
more shared, implicit, slightly less global, variables and that doesn’t seem 
like it is going to end well. Implicit connections in a binding will fail in, 
perhaps mysterious ways if you ever include any sort of concurrency: even 
things as simple as asynchronous logging can start logging wrong values or 
missing values.

Can you somehow render the global connection inoperable in some way? Perhaps 
redefine it, point it at a data source that doesn’t exist or… by some other 
hook-or-crook have it fail in a loud, grotesque manner if it is touched?


R.




> On 31 Jul 2015, at 01:54, J. Pablo Fernández <pup...@pupeno.com> wrote:
> 
> Hello James,
> 
> Thanks for your answer. I do understand your point. Pure functions are easier 
> to reason about and my use of dynamic here breaks that purity. I'm not doing 
> it lightly. It already happened to me, that one of those functions that was 
> running inside the transaction, was not passed the transaction connection and 
> instead got the global one and the failure was silent and very hard to debug, 
> and this was with a project that has less than 200 lines of code. I'm trying 
> to find patterns that will work when this project has 200k lines of code.
> 
> For me, the thing is, I have a traditional relational database here, this is 
> already far from pure. For example, calling (db/create-user 
> "pup...@pupeno.com <mailto:pup...@pupeno.com>") twice will not only not 
> return the same thing the second time, it'll actually raise an exception the 
> second time. Also, the database connection is *global state* unless each 
> function creates its own connection, which would be terrible. So, this global 
> state also breaks functional purity.
> 
> The problem with the second aspect of breaking purity as far as I can see is 
> this: at some point, this global state has to be picked up and used, so at 
> some point a function will *not* get a database connection passed to it but 
> *will* access the database by using this global connection. I haven't 
> advanced this project enough to say this with 100% certainty, but, I think 
> there's going to be more than one function like that and at some point I'll 
> need to have one inside the other so I need them to be composable. Let me 
> show you a naive example:
> 
> db/create-user is the low level database function that creates a record in 
> the user table
> user/create is the function used to create a user, it takes care of, for 
> example, encrypting the password.
> account/register is the function to register a new user, it takes care of 
> creating a user but also validation, sending a welcome email and so on.
> 
> So each function calls the predecessor there and would pass the database 
> connection, account/register, being the entry point, would grab it from the 
> global state so it doesn't get a connection passed to it. So far, a lot of it 
> looks like pure functions (let's ignore the fact that a database breaks that 
> purity). The problem arises when I get another function, account/invite, that 
> is used to register a bunch of people one after the other, so that 
> account/invite would call account/register many times. The problem is that 
> account/invite *can't* start a transaction and have account/register and all 
> its inner functions use that transaction when that makes a lot of sense.
> 
> To make account/register composable it needs to accept an optional database 
> connection and use that one if it's present, or the global one if it's not. 
> Every time a function does that there's a high risk of picking the wrong 
> database and account/invite and account/register shouldn't be dealing with 
> database connection management. That feels to me like lower level details 
> leaked into higher level abstractions.
> 
> Now, I know this is a naive example and you could push the grabbing of the 
> global connection higher and higher, as long as the example is naive and 
> simple like this, but it does represent what in my experience is the reality 
> of web application development at least in another languages and I haven't 
> seen anything to make me think Clojure will be radically different here (at 
> least when using a patterns such as compojure).
> 
> So yes, it's not purely function but with a database that's already 
> impossible and if I wanted purely functional I would probably be using 
> Haskell instead of Clojure. What I like about Clojure is this: 
> 
> Clojure is a practical language that recognizes the occasional need to 
> maintain a persistent reference to a changing value and provides 4 distinct 
> mechanisms for doing so in a controlled manner - Vars, Refs, Agents and Atoms.
> 
> I'm just trying to be practical here. But I'm new and I'm not sure if an atom 
> that is a dynamic var has some hidden issues that I'm not seeing (other than 
> the fact of it being state that changes and that I have to manage explicitly 
> because the language is not protecting me from shooting myself in the foot 
> with it).
> 
> Does it make sense?
> 
> 
> 
> On 31 July 2015 at 03:16, James Reeves <ja...@booleanknot.com 
> <mailto:ja...@booleanknot.com>> wrote:
> On 31 July 2015 at 01:44, J. Pablo Fernández <pup...@pupeno.com 
> <mailto:pup...@pupeno.com>> wrote:
> I found passing around the database connection to each function that uses it 
> very error prone when you are using transactions as passing the wrong one 
> could mean a query runs outside the transaction when in the source code it is 
> inside the with-db-transaction function. So I ended up defining the db 
> namespace like this:
> 
> (ns db)
> 
> (defonce ^:dynamic conn (atom nil))
> 
> (defn connect!
>   (reset conn (generate-new-connection)))
> 
> (defn run-query 
>   [query] (run-query query @conn)
>   [query conn] (run-the-query-in-connection query conn))
> 
> This style of code is generally considered to be unidiomatic in Clojure. The 
> reason for this is that it significantly increases complexity, and Clojure is 
> about reducing complexity where possible.
> 
> Consider a function like:
> 
>   (defn find-by-id [conn id]
>     (sql/query conn ["SELECT * FROM foo WHERE id = ?" id]))
> 
> The output of this function is affected by its arguments (and by the state of 
> the database the connection is associated with), which is passed by its 
> caller.
> 
> Now consider a function like:
> 
>   (defn find-by-id [id]
>     (sql/query @conn ["SELECT * FROM foo WHERE id = ?" id]))
> 
> The output of this function is affected by its arguments... and by anything 
> that touches the global conn var, which could literally be anything in your 
> program, in any namespace, in any function, in any thread.
> 
> The more ways in which a function has, the more "complex" it is. This is why 
> Clojure prefers immutable data over mutable data, and why function arguments 
> are generally preferred over dynamic vars.
> 
> The problem of accidentally calling a database connection directly inside a 
> transaction is a difficult one, but I don't think the solution is to add more 
> complexity. An alternative solution would be to take the original database 
> connection out of scope, by moving your transaction code to a separate 
> function:
> 
>    (defn do-things* [tx]
>      (do-foo tx)
>      (do bar tx)
>      (do baz tx))
> 
>    (defn do-things [db-spec]
>      (sql/with-db-transaction [tx db-spec]
>        (do-things* tx)))
> 
> If this is still too prone to error, you could also automate this pattern 
> with a function:
> 
>   (defn wrap-transaction [f]
>     (fn [db-spec & args]
>       (sql/with-db-transaction [tx db-spec]
>         (apply f tx args))))
> 
>   (def do-things
>     (wrap-transaction do-things*))
> 
> - James
> 
> -- 
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com 
> <mailto:clojure@googlegroups.com>
> Note that posts from new members are moderated - please be patient with your 
> first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com 
> <mailto:clojure%2bunsubscr...@googlegroups.com>
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en 
> <http://groups.google.com/group/clojure?hl=en>
> --- 
> You received this message because you are subscribed to a topic in the Google 
> Groups "Clojure" group.
> To unsubscribe from this topic, visit 
> https://groups.google.com/d/topic/clojure/fRi554wbPSk/unsubscribe 
> <https://groups.google.com/d/topic/clojure/fRi554wbPSk/unsubscribe>.
> To unsubscribe from this group and all its topics, send an email to 
> clojure+unsubscr...@googlegroups.com 
> <mailto:clojure+unsubscr...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout 
> <https://groups.google.com/d/optout>.
> 
> 
> 
> -- 
> J. Pablo Fernández <pup...@pupeno.com <mailto:pup...@pupeno.com>> 
> (http://pupeno.com <http://pupeno.com/>)
> 
> -- 
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with your 
> first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en 
> <http://groups.google.com/group/clojure?hl=en>
> --- 
> You received this message because you are subscribed to the Google Groups 
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to clojure+unsubscr...@googlegroups.com 
> <mailto:clojure+unsubscr...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout 
> <https://groups.google.com/d/optout>.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to