On 5 August 2015 at 14:03, Dmitri <[email protected]> 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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/d/optout.