integrant

Ashwin Bhaskar 2023-11-10T06:39:46.159209Z

hi folks, I am trying out Integrant library for state management. It's quite intuitive when it comes to managing (starting/halting) servers but I am confused regarding a certain use case. I want to use integrant to instantiate and destroy an object. As usual I defined defmethods init-key and halt-key! . The problem is that I want a reference to the initialised object from init-key . Is there an idiomatic way to do this? Should I just have it available in an atom during init-key ?

(defmethod ig/init-key :my-space/my-key [_ opts]
  (let [account-id (:account-id opts)
        license-key (:license-key opts)]
    (MyObject. account-id license-key)))

;;How do I use the instantiated my object?

Kirill Chernyshov 2023-11-10T08:30:24.269709Z

from where you want access myObject?

Ashwin Bhaskar 2023-11-10T08:32:12.433379Z

(defmethod ig/init-key :my-space/my-key [_ opts]
  (let [account-id (:account-id opts)
        license-key (:license-key opts)]
    (MyObject. account-id license-key)))

;;Function in same namespace
(defn fetch-name
  [id]
  ;;use MyObject Here)

Kirill Chernyshov 2023-11-10T08:35:59.682719Z

you can keep the initiated system available in some namespace. (def system (-> config (ig/prep) (if/init))) your object will be available under some key

Ashwin Bhaskar 2023-11-10T08:56:27.966869Z

wouldn't that lead to a cyclic dependency? • system namespace requires myobject name space (for it to instantiate the dependencies) • My object namespace requires system name space to get MyObject

Kirill Chernyshov 2023-11-10T08:57:39.483009Z

I'm not follow, sorry

weavejester 2023-11-10T11:19:53.584989Z

Typically systems created by Integrant will have some equivalent to a main loop. Perhaps this is a server of some kind, or perhaps a scheduler. Presumably fetch-name in your example will be called directly or indirectly from this component.

weavejester 2023-11-10T11:21:32.280769Z

For example, if you had a server:

{:my-space/server {:my-object #ig/ref :my-space/my-key}
 :my-space/my-key {:account-id ..., :license-key ...}}

weavejester 2023-11-10T11:24:03.756069Z

Sometimes a server will have subhandlers, so you could also write it:

{:my-space/server {:handlers [#ig/ref :my-space/my-handler]}
 :my-space/my-handler {:my-object #ig/ref :my-space/my-key}
 :my-space/my-key {:account ..., :license-key ...}}

weavejester 2023-11-10T11:25:01.652789Z

However, the point is to avoid global references, and instead make every dependency explicit, passed either via Integrant as a reference, or as a function argument.

(defn fetch-name [my-object id]
  ...)

weavejester 2023-11-10T11:25:19.410469Z

Does that make sense?

Ashwin Bhaskar 2023-11-10T11:27:18.288039Z

@weavejester yes makes sense. If I understood this right, I have to pass all the dependencies top down. So I will have to define my edn in such a way that server depends on MyObject. This way I can pass MyObject from server all the way down to routes and handlers (and eventually to fetch-name ) Is that right?

weavejester 2023-11-10T11:28:08.364529Z

Yep, exactly. There should be a clear, traceable graph of dependencies.

weavejester 2023-11-10T11:29:11.110179Z

There are state management systems that use global variables, such as Mount, but Integrant is opinionated about making dependencies explicit.

Ashwin Bhaskar 2023-11-10T11:30:51.861209Z

yes understood. I can see where you are coming from. This definitely requires a shift in perspective. I was just watching your talk https://skillsmatter.com/skillscasts/9820-enter-integrant-a-micro-framework-for-data-driven-architecture-with-james-reeves and it gets clearer. Thanks a lot for making this library!

weavejester 2023-11-10T11:31:17.267849Z

You're welcome 🙂