Rob,

The transactions are not global, the transactions are local. Connections
are global and there's no way around it with the constraints of a
traditional RDBMs. Your idea of making the global connection unavailable
for code that's in the context of a transaction would prevent the errors
I'm trying to prevent but you would still required to pass the connection
around, which in a webapp, means that the whole chain of function calls,
pretty much everything, would have a database connection. That is ugly in
some cases, impossible in others.

A piece of code that would be impossible is to enclose each test in a
transaction using clojure.test:
http://stackoverflow.com/questions/31735423/how-to-pass-a-value-from-a-fixture-to-a-test-with-clojure-test

Furthermore, I don't think this solution gains you any purity, you still
have a global estate and you are hiding it away when starting a
transaction. My proposal was to make it work instead of hiding it. They are
rather equivalent from the complexity point of view.

On 1 August 2015 at 18:29, Rob Lally <rob.la...@gmail.com> wrote:

> 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") 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> wrote:
>
>> On 31 July 2015 at 01:44, J. Pablo Fernández <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
>> 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 a topic in the
>> Google Groups "Clojure" group.
>> To unsubscribe from this topic, visit
>> 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.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
>
>
> --
> J. Pablo Fernández <pup...@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
> ---
> 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.
>
>
> --
> 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 a topic in the
> Google Groups "Clojure" group.
> To unsubscribe from this topic, visit
> 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.
> For more options, visit https://groups.google.com/d/optout.
>



-- 
J. Pablo Fernández <pup...@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
--- 
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