Fork me on GitHub
#sql
<
2022-10-20
>
stopa14:10:49

Hey team! Noob question:

(declare pool) 
// ...
(defn start []
  (def pool (connection/->pool HikariDataSource
                               (config/get-db-config)))
  (.close (next-jdbc/get-connection pool)))

(defn stop []
  (.close pool))

(defn restart []
  (stop)
  (start))

(next-jdbc/select pool ["SELECT * ..."])
I am trying to set up a next-jdbc with a connection pool. ^ This was my approach. I avoided component / mount; I don’t think we really need it yet. Question for you: Is there some subtle bug I am missing, or is what I wrote fine? I saw some people define pool as a dynamic variable. I assume this is so they can redefine pool for transactions. But I don’t need this, as every function that talks to the db accepts conn as an argument.

seancorfield14:10:01

Don't use def inside defn.

seancorfield14:10:50

If you really want a global Var (which I think is a bad idea), you can do this without the weird def-in`-defn` thing:

(def pool nil)

(defn start []
  (alter-var-root #'pool (constantly (connection/->pool ..)))
  (.close (next-jdbc/get-connection pool)))
I assume you're doing that last line to warm up the pool, at startup, rather than letting it do so lazily?

seancorfield14:10:43

(and the fact that the idiomatic way to modify pool's value is so ugly should be a hint that global Vars are intentionally unpleasant to work with)

stopa14:10:32

Gotcha, thanks Sean! Would you use something like an atom for this?

seancorfield14:10:35

My pattern is to pass stuff like this through the call chain, to be honest.

👍 1
seancorfield14:10:40

And then I have a global Var just for use with the REPL (and never reference it anywhere else).

stopa14:10:52

That’s fair! Also one more curious question: Why did you use constantly?

seancorfield15:10:21

alter-var-root takes a function to apply. So constantly makes this like "assignment" rather than an update.

❤️ 1
seancorfield15:10:37

(and signals that we're doing something "ugly")

❤️ 1
seancorfield15:10:41

For pretty much everything I write, I have more than just a "thing" that needs initializing (e.g., connection pool, web server, caching), so I use Component pretty much from the get-go.

stopa15:10:16

This makes a lot of sense! Thanks Sean — learned a bunch.

seancorfield15:10:41

So I have an app Component, that contains the db connection pool, the server, caches, etc, and I pass app through the call chain (or just parts of it as needed).

❤️ 1
seancorfield15:10:02

Then my -main builds the "production" Component and starts it and uses alter-var-root to put it in the REPL global Var (in case I need to REPL into the process while it is running on QA/production). And I have RCFs as needed that have code to build, start, and stop the "dev" Component, that deliberately does not update the global Var so I avoid the temptation to write code that depends on the global while working in dev mode.

❤️ 1
stopa15:10:36

Okay, noting to play with component and take a longer gander at usermanager-example. You rock Sean!

seancorfield15:10:02

Example from work:

(comment
  (def app "Admin App" (component/start (build-system 8081)))

  (require '[next.jdbc :as jdbc])
  (def ds (-> app :database :pooled-db :datasource))
  (jdbc/execute! ds ["select * from user where siteid = 45"])
  (jdbc/execute! ds ["select * from skywriting_admin"])
  (jdbc/execute! ds ["show index from loguserphoto"])
  (jdbc/execute! ds ["describe loguseragent"])
  (component/stop app))

seancorfield15:10:01

And the -main for that app looks like this:

(defn -main
  "Start Jetty and run the Admin Server app."
  [& [port]]
  (let [port (or port (get (System/getenv) "PORT" 8081))
        port (cond-> port (string? port) Integer/parseInt)
        running-system (component/start (build-system port))]
    (alter-var-root #'admin-app/sys (constantly running-system))
    (alter-var-root #'admin-app/metrics-request-name
                    (constantly "Admin Requests"))
    (start-telemetry (-> running-system :application :worldsingles-application)
                     "admin" "Admin Server"
                     `admin-web/probe)
    ;; wait for web server to shutdown
    (-> running-system :web-server :shutdown deref)))
Note the admin-app aliased ns containing the REPL global Vars.

seancorfield15:10:46

(I edited the RCF a bit so the -> is a little different than it actually is in the -main but in reality they are the same)

seancorfield15:10:11

Our Application Component contains a Database Component that holds multiple connection pools as c.j.j-compatible db-specs -- hence the :datasource selection to get a next.jdbc-compatible datasource.

seancorfield15:10:35

(since our app uses both libraries -- c.j.j in old code and next.jdbc in new code)

seancorfield15:10:34

And build-system includes the actual "web application" function that runs everything.