If I were writing it, I'd first start by creating a protocol of all the database functions I'd need.
(defprotocol Database
(list-states [db])
(list-positions [db])
(get-company-name [db]))
I'd then extend by database component record with this protocol:
(extend-type DatabaseComponent
Database
(list-states [{:keys [conn]} ...)
(list-positions [{:keys [conn]} ...)
(get-company-name [{:keys [conn]}])
The main reason for using a protocol, and not just writing the functions directly, is that it makes it easier to create a mock database for unit testing purposes.
Next I'd create a function to just pull all the relevant data into a map:
(defn fetch-index-data [db-component]
{:states (list-states db-component)
:positions (list-positions db-component)
:company-name (get-company-name db-component)})
Then a view to receive this information:
(defn view-index [{:keys [states positions company-name]}]
(html ...))
This distinction between fetching the data and viewing it helps define an exact boundary between I/O and processing the resulting data. We can see at a glance what data a view needs, rather than the I/O functions being buried in the view code. And it's also easier to test, of course.
Rather that pass the components via the request map, I'd use a closure instead:
(defn endpoint [{:keys [db]}]
(routes
(GET "/" [] (view-index (fetch-index-data db)))
...))
This is the approach I use in
Duct, a template/micro-framework for structuring component-based web apps. Not only is it a little faster (because we only have to destructure the component once), in my view it's a little cleaner as well.
- James