I agree that wrapping the functions is a sensible approach. Using
wrap-transaction is precisely how I ended up doing it with the conman yesql
wrapper https://github.com/luminus-framework/conman
The approach I took there is to have the generated functions use the
connection atom, and have with-transaction rebind it to the transactional
connection within its scope. However, the functions also accept an explicit
connection, and with-transaction also provides explicit access to the
transactional connection:
(with-transaction [t-conn conn]
(jdbc/db-set-rollback-only! t-conn)
(create-user!
{:id "foo"
:first_name "Sam"
:last_name "Smith"
:email "[email protected]"})
(get-user {:id "foo"}))
This approach works for testing, since the connection can be passed
explicitly, but I would argue that in practice it's better to use a
separate test database instead of the dev instance.
On Wednesday, August 5, 2015 at 11:11:19 AM UTC-4, James Reeves wrote:
>
> On 5 August 2015 at 14:03, Dmitri <[email protected] <javascript:>>
> 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.