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.