I found passing around the database connection to each function that uses it very error prone when you are using transactions as passing the wrong one could mean a query runs outside the transaction when in the source code it is inside the with-db-transaction function. So I ended up defining the db namespace like this:(ns db)(defonce ^:dynamic conn (atom nil))(defn connect!(reset conn (generate-new-connection)))(defn run-query[query] (run-query query @conn)[query conn] (run-the-query-in-connection query conn))
Clojure is a practical language that recognizes the occasional need to maintain a persistent reference to a changing value and provides 4 distinct mechanisms for doing so in a controlled manner - Vars, Refs, Agents and Atoms.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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 a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/fRi554wbPSk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
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+u...@googlegroups.com.
I am away from the code at the moment, but is there any reason why the dynamic connection can’t be private? This goes some way to providing safety.
For me, the thing is, I have a traditional relational database here, this is already far from pure. For example, calling (db/create-user "pup...@pupeno.com") twice will not only not return the same thing the second time, it'll actually raise an exception the second time. Also, the database connection is *global state* unless each function creates its own connection, which would be terrible. So, this global state also breaks functional purity.The problem with the second aspect of breaking purity as far as I can see is this: at some point, this global state has to be picked up and used, so at some point a function will *not* get a database connection passed to it but *will* access the database by using this global connection.
That is indeed a useful library for wiring things up at init-time, but it doesn't help at all for run-time wiring, like transactions.
That's how I normally use dynamic vars. Some people afraid of using it, but it is like a knife - If you know how to use it, it is useful to remove unnecessary complexity.
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+u...@googlegroups.com.
Hello Clojurians,I found passing around the database connection to each function that uses it very error prone when you are using transactions as passing the wrong one could mean a query runs outside the transaction when in the source code it is inside the with-db-transaction function.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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 a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/fRi554wbPSk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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 a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/fRi554wbPSk/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
I’m also a little confused by your suggestion that it would be impossible to enclose each test in a transaction. The article you point to shows one way.
James,I'm not new to functional programming and I understand the principles and why they are good.
I worked in Haskell, Erlang and other Lisps before. Even if only a tiny portion of my codebase deals with the database, I still need a pattern for that part of the codebase.It is very easy to say "this is dirty, you should not do this" without offering an alternative solution and by alternative solution I mean one that applies to the pattern of making web applications that are database heavy.
Think of your traditional CRUD application, you can say "quarentine the code that deals with the database" and yes, you can do that, but it still is 90% of the code.
I have heard this approach before, but I have never seen how it works in
real life.
For example, what about 'selects' - where do they happen?
What about if my updates are not independent (e.g. if the first
update works then do the second update otherwise do this completely
different thing?).
For simple workflows I can see the elegance. For non-trivial workflows
the problem is that _accessing_ the DB and _switching_ on the DB logic
tends to be all through the workflow.
I am genuinely interested in the answers to this as yes, the described
approach has some great benefits.
My understanding is that the problem is actually caused by the stateless nature of the functions.
Since the function accepts the connection as a parameter it's up to the user of the function to ensure that it's passed the correct connection.
Every functional solution presented in this thread suffers from this same fundamental problem that the function is not aware of the context it's being run in.
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.
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.
(with-transaction [t-conn conn]
(jdbc/db-set-rollback-only! t-conn)
(create-user!
{:id "foo"
:first_name "Sam"
:last_name "Smith"
:email "sam....@example.com"})
(get-user {:id "foo"}))
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/conmanThe 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
{:profiles/dev {:env {:database-url "jdbc:postgresql://localhost/myapp_dev?user=db_user_name_here&password=db_user_password_here"}} :profiles/test {:env {:database-url "jdbc:postgresql://localhost/myapp_test?user=db_user_name_here&password=db_user_password_here"}}}
So when you're testing, presumably you use a dynamic binding to override the global connection to the test database?