Hello integrant folks!
I’m struggling trying to use a different impl of a “component” depending of env… Simple use case: I have a mailer component for production
environment and a dev
mailer component that only print the message
… This mailer component acts as a dependency for other components …
in https://github.com/stuartsierra/component library this is really easy to achieve, but I don’t know which is the preferred/existent approach using integrant
I’ve looked to the docs with no success 😞
Thanks in advance!
@tangrammer I have this exact scenario! I’m using Integrant with Duct, but what Duct does is have a dev.edn config that’s included/merged in when in development mode. So I override the component and override any dependent keys references.
Another way of doing it is have a component that reads in the environment, and then have the mailer component depend on that one, and switch dev/prod impl based on the environment component dependency.
Guessing here. I think you create your config.edn {:component {:mailer #ig/ref :mailer}}
then (derive :mailer :prod/mailer) (derive :mailer :test/mailer) then merge your test.edn config to overwrite you config.edn.
@dadair could you expand that approach “So I override the component and override any dependent keys references.” ?
@grierson I’ll give a try to that approach too 🙂
Sure, so I have a config.edn
with:
:mandala.lib.email/email-fn {}
:mandala.lib.email/postman
{:ssl true
:host ...
...
:email-fn #ig/ref :mandala.lib.email/email-fn}
and I have a dev.edn
with:
:mandala.lib.email/email-fn-dev {}
:mandala.lib.email/postman
{:email-fn #ig/ref :mandala.lib.email/email-fn-dev}
The merging of the config files is handled by Duct
via :duct.core/include ["mandala/config"]
in dev.edn
and by the following in a dev.clj
namespace:
(defn read-config []
(duct/read-config (io/resource "dev.edn")))
(integrant.repl/set-prep! (comp duct/prep read-config))
So in development mode the :email-fn
key is referencing a dev-mode console printer, while in production (via standard configuration in config.edn
that isn’t overridden in prod) the :email-fn
key referencing a true mailer.Without using Duct, you could have a standard integrant component that returns the environment (something like (defmethod ig/init-key ::env [k opts] (System/getenv ".."))
then you can pass that component into the mailer (defmethod ig/init-key ::mailer [k {:keys [env]}] (if (dev? env) dev-mailer prod-mailer))
.
thanks for the example! I’ll try it right now 🙂
@dadair in production
environment will I have email-fn-dev
and email-fn
in the system
??
so both will be initialised too?
In production you would only read the “config.edn” file so you would only have “email-fn” (if you set it up that way)
good point 🙂 but in development, will i have both?
Yes, though the production key wont be referenced/used. That’s why I extracted the function that performs the side effect, it might be created but wont be called
If email-fn performed some side effect at init-time, you would have a potential problem
@grierson besides the derivation stuff …
(derive :app.emailer/dev-emailer :app.emailer/emailer )(derive :app.emailer/prod-emailer :app.emailer/emailer )
,what do i need to add to dev.edn and/or config.edn? I’m getting IllegalArgumentException No method in multimethod 'init-key' for dispatch value: :app.emailer/emailer clojure.lang.MultiFn.getFn (MultiFn.java:156)
😬yep :thinking_face: I think it should exist a clear and straight forward approach …
The easiest/simplest approach is passing the env into the single component definition and use that to configure the mailer appropriately for that env
Fairly straightforward
I was wrong you don't need to have the :app.emailer/emailer key in your config.
but in this case i can’t keep my dev things aside my prod things … don’t i ? :thinking_face:
{:component {:mailer #ig/ref :mailer}} (derive :mailer :app.mailer/mailer)
or (derive :mailer :app.mailer/test-mailer) for test
the :mailer just acts as indirection so you can swap out the live and test version of your mailer.
Thanks again! (I’ll try in a few 🙂 )
@dadair thanks a lot for your help!! I have to leave now 🙂
In this case you are mixing, sure, but it’s still functional/transparent as it’s being passed the environment.