With component libraries such as Component/Integrant, when a function has a dependency on part of the system state, we usually inject the dependency and put it in a map.
Assuming repo
is a component with CRUD functions for customers, we would have something like:
;; to call (get-contact-by-id id), we do something like
(let [{:keys [repo]} sys-map]
((:get-contact-by-id repo) id))
I’d like to know if I do something like the example below to make the function call more natural, like (repo/get-contact-by-id id)
, what problems I might have in the long run.
Since my knowledge is still quite limited, I’m not sure about the consequences.
So, it would be good to get others’ opinions on this.
The basic idea is to have some dummy functions in the namespace, and
then alter the value of those vars with actual implementations (already injected functions with fewer arguments) during component initialization.When the system is ready, other modules can just call the functions as we normally would, without needing to look up values in a map.
The idea might be silly, but I’m just exploring if it can possibly work without many negative consequences.
Vars are mutated globally, but only once during initialization. Could this still be problematic somehow?
Regarding testing, I am not sure if it would make much harder. I think I can still test the internal version of the functions quite easily.
(ns contactappx.repository.contact-repo
"Repository component for contact"
(:require [donut.system :as ds]
[next.jdbc :as jdbc]))
(defn find-contact-by-id [id]
(throw (Exception. "Function not implemented.")))
(defn create-contact! [contact]
(throw (Exception. "Function not implemented.")))
(defn ^:internal _find-contact-by-id [dsc id]
"INTERNAL: Search contact by id using the given data source"
(println "Hello world2"))
(defn ^:internal _create-contact! [dsc contact]
"INTERNAL: Create a contact using the given datasource"
nil)
(def ContactRepoComp
{::ds/start
(fn [{{:keys [dsc]} ::ds/config}]
(alter-var-root
#'contactappx.repository.contact-repo/find-contact-by-id
(constantly (fn [id] (_find-contact-by-id dsc id))))
(alter-var-root
#'contactappx.repository.contact-repo/create-contact!
(constantly (fn [id] (_create-contact! dsc id)))))
::ds/config {:dsc {}}})
;(alter-var-root
; #'contactappx.repository.contact-repo/find-contact-by-id
; (constantly (partial _find-contact-by-id dsc)))
(comment
(def contact-repo (ds/start {::ds/defs {:repo {:contact-repo ContactRepoComp}}}))
(println contact-repo)
(find-contact-by-id 3)
)
Edited: After reading this blog, I think I didn’t properly understand the reason why component libraries are used. Having the datasource as hidden state in the repository functions is already a bad idea I guess.