On 5 August 2015 at 14:03, Dmitri <dmitri.sotni...@gmail.com> wrote:

> What I'm talking about is whether it's a better pattern to leave a
> repetitive and error prone task to the user or encapsulate it in a single
> place. The whole discussion boils down to following options.
>
> The first option is that we keep functions pure and the connection is
> passed as a parameter by the person writing the code (the user). With this
> approach the burden is squarely on the user to remember to pass the correct
> connection to the function. This becomes error prone for things like
> transactions where the developer has to pass the transaction connection as
> opposed to the normal database connection. The worst part in this scenario
> is that the code will run except it won't run transactionally. This is a
> bug that is difficult to catch as it only surfaces in cases where the
> transaction should be rolled back.
>

It's worth pointing out that you don't need to use dynamic or global vars
to avoid this scenario. You could just remove the original database
connection from scope:

  (defn foobar* [tx]
    (foo tx)
    (bar tx))

  (defn foobar [db]
    (sql/with-transaction [tx db] (foobar* tx))

Or shadow the original binding:

  (defn foobar [db]
    (sql/with-transaction [db db]
      (foo db)
      (bar db)))

Ideally you also want a way of testing this behaviour, even with dynamic or
global scope. If it's critical to your application that database operations
run in a transaction, you should have a way of verifying that.

For instance, you might use a protocol to factor out the operations on the
database:

  (defprotocol Database
    (wrap-transaction [db f])
    (foo db)
    (bar db))

  (defn foobar [db]
    (wrap-transaction db
      (fn [tx]
        (foo tx)
        (bar tx))))

This allows tests to be written to verify that foo and bar are called
within wrap-transaction, and to verify our production implementation of the
protocol correctly wraps the function f in a SQL transaction.

If you're writing something that depends upon behaviour that can't be
verified, you're going to run into problems no matter how you structure
your application.


> The alternative is to encapsulate the database connection management in
> the initialization logic in the namespace managing the connection. This way
> the query functions can be context aware and ensure that the correct
> connection is used automatically.
>

Do you mean storing the database connection in a global var, not just a
dynamically scoped one?

In such a case, how do you run tests without wiping the development
database? Do you run the tests in a separate process, or shut down the dev
server before running tests, or do you not mind if your development
database is cleared by your tests?

- 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