integrant

Steven Lombardi 2025-08-25T17:35:26.516779Z

This is more of a component related question, but I'm cross posting to this channel because I'm curious if Integrant may offer a solution to this that component does not. Or if this is relatively unexplored space that may justify a new solution. https://clojurians.slack.com/archives/C0GQAAKA9/p1756142887703779

weavejester 2025-08-26T12:37:55.813919Z

When you say "low level dependency changes might mean updating the system map of 20+ microservices" can you give an example of what you mean? Integrant has a few different tools for dealing with problems like these: inheritance in keywords, refsets and modules. But I think I need an example or some more information on the specifics before I can be sure.

Steven Lombardi 2025-08-26T16:09:19.808179Z

Right now, for all 20+ of our services, we do something like this, except there are many more components/dependencies involved:

(-> (component/system-map :component-1 ( ... ) :component-2 ( ... ) ... )
    (component/system-using 
      {:component-1 [:component-2 :another-component]
       :component-2 [ ... ]}))
There might be, for example, a common dependency subtree for the :database that's repeated across all 20+ system definitions. If we change the database's dependencies, we have to update all the systems. What I want is a separate system definition for the database that can be brought in, and when it changes, the source code of the 20+ systems stays stable, but they still get the updated dependency tree.

weavejester 2025-08-26T16:43:19.436419Z

Ah, I see now. Yes, that's exactly the problem that modules are designed to solve. A module effectively defines a subsystem that is merged into the system map. For example, you might have code that looks like:

;; Setup inheritance so that we can use the more generic
;; :app.database/sql key
(derive :app.database.sql/postgres :app.database/sql)

;; Create an expansion that is merged into the system
(defmethod ig/expand-key :app.module/database [_key _config]
  {:app.database.sql/postgres
   {:queue (ig/ref :app.queue/simple)
    :cache (ig/ref :app.cache/redis)}
   :app.queue/simple {...}
   :app.queue/cache {...}})

;; In your configuration:
(def config
  {:app.handler {:db (ig/ref :app.database/sql)}
   :app.module/database {}})

(defn -main []
  (-> config
      (ig/expand) ;; expand out modules
      (ig/init)   ;; start system
You can change around the dependencies (refs) in the module however you wish. You could even change the database key, as long as the new one derives from the same :app.database/sql.

Steven Lombardi 2025-08-26T17:05:22.168229Z

Can you have multiple levels of subsystems? e.g. 3 subs, merge 1 onto 2, then the result onto 3?

Steven Lombardi 2025-08-26T17:06:46.704299Z

And, if you can, I assume you don't hit key contention because unlike component, in integrant you can use fully qualified keys everywhere?

weavejester 2025-08-26T17:12:19.919339Z

Expansions are not applied recursively by default, but you can create a module that is a superset of other modules. For example:

(defmethod ig/expand-key :app.module/abc [_ opts]
  (merge (ig/expand-key :app.module/a opts)
         (ig/expand-key :app.module/b opts)
         (ig/expand-key :app.module/c opts)))
And yes, because top-level keys have to be qualified keywords, contention isn't usually an issue.

weavejester 2025-08-26T17:14:30.167319Z

You could also write a function that recursively called ig/expand until there are no more expansions (i.e. when the configuration no longer changes), but I didn't want that to be the default behavior of ig/expand, as I felt that could result in hard-to-debug issues.

Steven Lombardi 2025-08-26T17:39:15.355069Z

Sounds great. I'm going to explore this. Thanks so much for the guidance.