Multiple dependencies of same "type"
jakepearson opened this issue · 1 comments
jakepearson commented
Hi! Is there a way to define multiple components that implement some kind of "interface" and then be able to query a system for all the components that meet those criteria? I want to find all the matching components without knowing the list in advance. In my case, I have a bunch of components that can handle messages of different types. I want to have one component that listens for the messages then routes them to the correct handler.
I wrote a sample below that appears to be a working solution, but seems kind of yucky with the atom
. Ideas?
(ns teller.component-test
(:require [clojure.test :refer [deftest is]]
[com.stuartsierra.component :as component]))
(defrecord Accumulator []
component/Lifecycle
(start [this]
(assoc this :atom (atom {})))
(stop [this]
(assoc this :atom nil)))
(defrecord Handler [text accumulator]
component/Lifecycle
(start [this]
(let [lookup (:atom accumulator)]
(swap! lookup assoc (-> (str "handler-" text) keyword) text)
this))
(stop [this]
(update-in this [:handlers] dissoc :lookup)))
(defrecord Handlers [accumulator]
component/Lifecycle
(start [this]
(let [lookup @(:atom accumulator)]
(assoc this :lookup lookup)))
(stop [this]
(assoc this :lookup nil)))
(defrecord Mapper [handlers]
component/Lifecycle
(start [this]
(println handlers)
this)
(stop [this]
this))
(defn build-system []
(component/system-map
:accumulator (map->Accumulator {})
:handler-a (component/using (map->Handler {:text "a"})
[:accumulator])
:handler-b (component/using (map->Handler {:text "b"})
[:accumulator])
:handlers (component/using (map->Handlers {})
[:accumulator])
:mapper (component/using (map->Mapper {})
[:handlers])))
(defn start []
(component/start (build-system)))
(deftest should-start
(let [system (start)]
(is (= {:handler-a "a"
:handler-b "b"}
(get-in system [:handlers :lookup])))))
jakepearson commented
Worked on it some more and came up with a solution we are pretty happy with ...
(ns teller.component-test
(:require [clojure.test :refer [deftest is]]
[com.stuartsierra.component :as component]
[taoensso.timbre :as log]))
(defprotocol MappableHandler
(get-label [this])
(get-handler-fn [this]))
(defrecord Handler [db label handler]
component/Lifecycle
(start [this]
this)
(stop [this]
this)
MappableHandler
(get-label [_this]
label)
(get-handler-fn [_this]
handler))
(defrecord Mapper [db handler-labels]
component/Lifecycle
(start [this]
; get the injected handlers
(let [handlers (into {}
(map (fn [label]
(let [component (get this label)
handler-label (get-label component)
handler-fn (get-handler-fn component)]
[handler-label handler-fn])))
handler-labels)]
(log/info "handlers" handlers)
(assoc this :handlers handlers)))
(stop [this]
(assoc this :handlers nil)))
(defn create-system []
(component/system-map
:a (component/using (->Handler nil :handler-a (fn [_x] "a"))
[:db])
:b (component/using (->Handler nil :handler-b (fn [_x] "b"))
[:db])
:db {:conn "Database"}))
(defn add-mapper [system]
(log/info "add-mapper")
(let [handler-labels (into []
(comp (filter (fn [[_label component]]
(log/info "component" component)
(satisfies? MappableHandler component)))
(map (fn [[label _component]]
label)))
system)
dep-comps (into [:db] handler-labels)]
(log/info "after")
(assoc system :mapper (component/using (->Mapper nil handler-labels)
dep-comps))))
(defn start []
(component/start (add-mapper (create-system))))
(deftest should-start
(let [system (start)]
(is (= [:handler-a :handler-b]
(-> system
(get-in [:mapper :handlers])
keys)))))