Ah, that makes sense. Thanks for the thorough response! On Thursday, April 16, 2015 at 7:00:16 AM UTC-5, Stuart Sierra wrote: > > 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.