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 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