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      "sam.sm...@example.com"})
  (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 <dmitri....@gmail.com <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 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