weavejester / integrant

Micro-framework for data-driven architecture

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Component decorator

darkleaf opened this issue · comments

For example I have following system:

(def config
  {::logger         {}
   ::sms-sender     {}
   ::component-1    {:sms-sender (ig/ref ::sms-sender)}
   ;; ...
   ::component-1000 {:sms-sender (ig/ref ::sms-sender)}})

(defmethod ig/init-key ::logger [_ _]
  #(prn :log %))

(defmethod ig/init-key ::sms-sender [_ _]
  #(prn :sms %))

(defmethod ig/init-key ::component-1 [_ {:keys [sms-sender]}]
  (fn []
    (sms-sender "Hi! I'm 1")))

;; ...

(defmethod ig/init-key ::component-1000 [_ {:keys [sms-sender]}]
  (fn []
    (sms-sender "Hi! I'm 1000")))

And I want to add logged-sms-sendercomponent that logs every sms. I can replace (ig/ref ::sms-sender) with (ig/ref ::logged-sms-sender) 1000 times but I don't want to do this.

Can I decorate a component without changing reference to it?

I'm not sure what you mean by "decorate" in this case, but if you want to replace ::sms-sender with ::logged-sms-sender, then all you need to do is:

(derive ::logged-sms-sender ::sms-sender)

Then ensure that ::logged-sms-sender is the only key that derives from ::sms-sender in your map.

"decorate" means to use decorator pattern.

I have defined sms-sender and logger components.
And I want to create a new logged-sms-sender.

May be like this:

;; I guess this isn't work with integrant
(defmethod ig/init-key ::logged-sms-sender [_ {:keys [logger sms-sender]}]
  (fn [msg]
    (logger msg)
    (sms-sender msg)))

Or I can use ugly get-method:

(derive ::logged-sms-sender ::sms-sender)

(defmethod ig/init-key ::logged-sms-sender [_ {:keys [logger], :as params}]
  (fn [msg]
    (let [sms-sender ((get-method ig/init-key ::sms-sender) params)]
      (logger msg)
      (sms-sender msg))))

You don't need to use get-method. You can just write:

(defmethod ig/init-key ::logged-sms-sender [_ {:keys [logger] :as opts}]
  (let [sms-sender (ig/init-key ::sms-sender opts)]
    (fn [msg]
      (logger msg)
      (sms-sender msg))))

If you want a generic logging "decorator" then you could write something like:

(defmethod ig/init-key ::logged [_ {:keys [logger fn]}]
  (fn [& args]
    (logger args)
    (apply fn args)))

And then in your configuration you could write:

{::logger     {}
 ::sms-sender {}
 [::logged ::logged-sms-sender] {:logger #ig/ref ::logger, :fn #ig/ref ::sms-sender}}

I liked the first example. Thanks for idea.

But I don't understand second example.

{::logger     {}
 ::sms-sender {}
 ::component-1 {:sms-sender #ig/ref ::sms-sender} 
 [::logged ::logged-sms-sender] {:logger #ig/ref ::logger, :fn #ig/ref ::sms-sender}}

::component-1 must get [::logged ::logged-sms-sender] as dependency.

You'd need to rename the keys in my last example; either by changing the references or renaming ::sms-sender key.