Hi Dan, The key to understanding what's happening here is to remember that `component/start` combines both dependency ordering *and* dependency injection.
Your "super system" looks like this just after it is constructed: {:system {:foo {}, :bar {}}, :system2 {:baz {}, :qux {}}} When you call `component/start` on this system, it dispatches to `start-system`, which will build the dependency graph to see that :system must be started before :system2. Next, start-system will call `component/start` on the component at :system. That component is itself a SystemMap, so it dispatches to `start-system`, which starts :foo and :bar. Now it's time to start :system2. start-system sees that :system2 has a dependency (`component/using`) on :system. So it will `assoc` :system into the component at :system2. Now the system looks like this: {:system {:foo {:started true}, :bar {:started true, :foo {:started true}}}, :system2 {:baz {}, :qux {}, :system {:foo {:started true}, :bar {:started true, :foo {:started true}}}}} Notice that :system2 now contains a complete copy of :system. The rest should be obvious. Starting :system2 will start :baz, :qux, *and* the nested copy of :system. So :system gets started again. :foo and :bar were already started, but `component/start` doesn't know that, so it starts them again. This is another reason I don't recommend nesting systems: the behavior is not obvious unless you deeply understand the model. There are 2 ways to prevent the repeated starting of :foo and :bar in this example. 1. Define a custom record for your "super system" implementing component/Lifecycle in a way that knows how to start the subsystems in the correct order without `assoc`ing their dependencies. 2. Define the `start` and `stop` methods of :foo and :bar to check if they have already been started before starting them again. (i.e. make them idempotent) -S On Wednesday, April 15, 2015 at 7:52:42 PM UTC+1, Dan Kee wrote: > > Sorry to resurrect an old thread with a somewhat tangential question but... > > I'm seeing strange behavior in nesting systems that I am hoping someone > can explain. I have two independent systems as components of a super > system, with an artificial dependency to attempt to enforce ordering of > starting them. When I start the super system, the first system starts, > then the second, then first system is started again and I don't understand > why. I've since realized much simpler, more obvious ways to accomplish > this, but I'd like to understand what I'm seeing. > > Code (apologies for the macro, but it keeps things brief): > > ``` > (ns component-debug > (:require [com.stuartsierra.component :as c]) ) > > (defn capitalize-symbol [s] > (symbol (clojure.string/capitalize (str s))) ) > > (defmacro make-example-component [name] > (let [capitalized-name (capitalize-symbol name)] > `(do > (defrecord ~capitalized-name [] > c/Lifecycle > (start [~name] > (println (str "Starting " '~name)) > (assoc ~name :started true) ) > > (stop [~name] > (println (str "Stopping " '~name)) > (assoc ~name :started false) ) ) > > (defn ~(symbol (str "new-" name)) [] > (~(symbol (str "map->" capitalized-name)) {}) ) ) ) ) > > (make-example-component foo) > (make-example-component bar) > (make-example-component baz) > (make-example-component qux) > > (defn new-system [] > (c/system-map > :foo (new-foo) > :bar (c/using > (new-bar) > [:foo] ) ) ) > > (defn new-system2 [] > (c/system-map > :baz (new-baz) > :qux (c/using > (new-qux) > [:baz] ) ) ) > > (defn new-super-system [] > (c/system-map > :system (new-system) > :system2 (c/using > (new-system2) > [:system] ) ) ) > > (defn -main [& args] > (c/start (new-super-system)) ) > ``` > > Output: > > ``` > Starting foo > Starting bar > Starting baz > Starting qux > Starting foo > Starting bar > ``` > > Thank you! > > --Dan > > On Wednesday, March 18, 2015 at 8:51:17 AM UTC-5, platon...@gmail.com > wrote: >> >> A possible implementation for the idea expressed in the previous post - >> https://github.com/stuartsierra/component/pull/25 >> >> >> On Wednesday, March 18, 2015 at 2:41:46 PM UTC+1, platon...@gmail.com >> wrote: >>> >>> I've also been investigating the nested system approach/problem. >>> >>> The primary use case that I have is composing subsystems which are >>> mostly independent modules using a higher order system to run in one >>> process. The modules themselves can be easily extracted into separate >>> applications thus becoming their own top level systems which makes it >>> desirable to keep their system maps intact. >>> >>> Components inside modules might depend on the *whole* modules, not >>> their constituent parts. This allows to have modules call each other >>> through the API's in-process as well as being easily replaced by remote >>> endpoints when separated into multiple processes. >>> >>> This mostly works except for the components depending on other >>> modules/systems, e.g.: >>> >>> (require '[com.stuartsierra.component :as cmp]) >>> >>> (defrecord X [x started] >>> cmp/Lifecycle >>> (start [this] (if started (println "Already started " x) (println >>> "Starting " x " " this)) (assoc this :started true)) >>> (stop [this] (println "Stopping " x " " this) this)) >>> >>> (def sys1 (cmp/system-map :x (cmp/using (X. "depends on dep" nil) >>> [:dep]))) >>> (def sys2 (cmp/system-map :y (cmp/using (X. "depends on sys1" nil) >>> [:sys1]))) >>> (def hsys (cmp/system-map :sys1 (cmp/using sys1 [:dep]), :sys2 >>> (cmp/using sys2 [:sys1]) :dep (X. "dependency" nil))) >>> >>> (cmp/start hsys) >>> >>> Starting dependency #user.X{:x dependency, :started nil} >>> Already started dependency >>> Starting depends on dep #user.X{:x depends on dep, :started nil, :dep >>> #user.X{:x dependency, :started true}} >>> >>> clojure.lang.ExceptionInfo: Error in component :sys2 in system >>> com.stuartsierra.component.SystemMap calling >>> #'com.stuartsierra.component/start >>> clojure.lang.ExceptionInfo: Missing dependency :dep of >>> clojure.lang.Keyword expected in system at :dep >>> >>> This happens because of the following: >>> 1. Dependency :*dep* of *sys1* is started in *hsys* >>> 2. *sys1* is started (:*dep* is started again, so the start/stop should >>> be idempotent) >>> 3. Dependency :*sys1* of *sys2* is started in *hsys* >>> 4. :*sys1* cannot be started as it depends on :*dep* which isn't >>> present in *sys2* >>> >>> This scenario could be supported by the Component library in several >>> ways: >>> >>> 1. introduce an IdempotentLifecycle protocol which will be respected by >>> the Component library. Implement the protocol for the SystemMap. >>> IdempotentLifecycles will not be started or stopped for the second time, >>> also their dependencies will not be updated if they are already started. >>> 2. do not fail if a component already has a dependency under the >>> specified key. This is a hack compared to the first solution, but I might >>> go with it in the short term. >>> >>> Stuart, what do you think about that? Would you consider a PR >>> implementing the first proposal? >>> >>> On Wednesday, March 18, 2015 at 10:18:36 AM UTC+1, Stuart Sierra wrote: >>>> >>>> >>>> On Tue, Mar 17, 2015 at 5:47 PM, James Gatannah <james.g...@gmail.com> >>>> wrote: >>>> >>>>> FWIW, we've been using something that smells an awful lot like nested >>>>> systems for months now. I never realized we weren't supposed to. >>>>> >>>> >>>> >>>> It's not that nested systems *never* work, but from what I've seen >>>> they cause more complications than they're worth. The 'component' model >>>> doesn't forbid it, but it does not support dependencies between components >>>> in different "subsystems." >>>> >>>> I've found it easier to keep system maps "flat" and use namespaced >>>> keywords to distinguish "subsystem" groups, even in large systems with 30+ >>>> components. >>>> >>>> –S >>>> >>>> -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups "Clojure" group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.