Thank you @dharrigan and @seancorfield for the help yesterday, I’ve now re-written my code given your suggestions and I no longer get timed out errors. Everything works smooth as butter, and I think I actually gained performance improvements.
You're most welcome. Feel free to shout up if you need any further help! 🙂
When querying data from the DB across multiple tables jdbc.next
will use those table names as the qualified name
. For example, a get-product
,query might return the following
{:product/id v
:price/amount v
:product/name v
:user/creator v}
My question is what do people think about renaming the qualified names to all be something like product/*
instead of the original table name? Is there value in this?The rationale I thought of was:
• avoid leaking implementation details
• easier for consumers to reference the keys
• support evolution of backend (contrived example: maybe amount
is derived from two tables in the future)
@tkjone As the documentation says: try to work with the default behavior for a while before you start fighting against it.
Hash maps with a mix of namespace qualifiers are perfectly idiomatic Clojure. People just aren't used to it yet.
The additional clarity is worth the effort, I assure you, partly because it makes you think more carefully about refactorings. It also makes any transformation you do between layers very explicit.
If you tell next.jdbc
to use a fixed :qualifier-fn
(such as using :product/*
) then you are still leaking your DB structure and/or query structure into your code but now you've disguised part of that, instead of thinking carefully about structures, layers, and boundaries.
Fair point! Do you feel these points also apply to the HTTP API layer (e.g. REST)? That is the boundary I was grappling with. For example
(get-products) ;; returns mixed namespace qualifiers
;; --- boundary ----
(defn http-handler []
(get-products)
(http-router
["api/products" http-handler]) ;; should this return mixed namespace qualifiers to consumers of the REST service?
@tkjone Most APIs have to traffic in unqualified names (for JSON interop etc) so I would expressly transform any internal data into the appropriate external format at that last stage. If you're using JSON, Cheshire and clojure.data.json
both have options to produce unqualified keys (but they have opposite defaults: one maintains qualifiers by default, the other does not).
My rule of thumb is: only transform qualified keys if you have to. So I tend to leave DB results as qualified/snake_case, for example, up to the point where I need them in an alternative format -- but often that "need" is a system boundary of some sort.
In some situations, I have a very explicitly "domain model format" that uses specific qualified/kebab-case names and I'll transform to/from other_qualified/snake_case at the boundary of that domain model code, but where it's "just data" as opposed to some specific entity I let it flow through the system (e.g., reporting against "log" tables and CRUD-based admin functionality all tends to maintain the DB-centric qualified/snake_case names).
Thanks @seancorfield!
(I'm afraid I'm somewhat notorious for my "it depends" answers when asked for specific guidelines 🙂 )
haha in this case it answered my main question and aligned to how i’m doing things as is, except now i’m confident with performing the transform at the boundary.
When using honeysql with next.jdbc, and only selecting info from a single table at a time, is it still normal to get namespaces keywords back e.g :table/column? It was my understanding that it would only be the case if I selected info from multiple tables in one query. Not that there’s anything wrong with it, but it’s just sort of of odd to write stuff like (get post :posts/title)
where I would instead like to write (get post :title)
. A sample code I use is:
(-> :select :*
:from :posts
sql/build
sql/format)
And then I simply pass the result to next.jdbc’s execute!
Yep 🙂
This is partially the conversation sean and I we’re having above.
Yup I read it and understood it made sense if selecting info from multiple tables, but I’m not doing that, which makes me feel like maybe I’m doing something wrong (or maybe not)
I’m guessing I have to use :qualifier-fn to get a desired result then?
You’re not doing anything wrong. It’s the default behaviour for jdbc.next
I really have to get better at reading the docs 😄 I finally found that :builder-fn as-unqualified-maps
does the trick perfectly.
@asko I'd strongly recommend working with the default behavior for a while and using qualified keywords -- they are idiomatic Clojure.
:posts/title
is, after all, semantically different to :people/title
(the latter being things like "Mr.", "Ms.")
There's a reason that as-unqualified-maps
is not the default behavior and is not introduced until later 🙂
"Relying on the default result set builder -- and table-qualified column names -- is the recommended approach to take"
Yes, I too took the initial approach of using as-unqualified-maps, but I'm now using the better approach of namespaced keywords, and slowly going back to fix the stuff that did use as-unqualified-maps
to use namespaced ones. It does pay off in the long run to adopt Sean's good advice there 😉
(I've updated my simple startrek repo to show how I remove the namespace, as in my simple contrived example I don't need it, but in more complicated programs, I do keep the namespace and work with it 🙂 )