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, [email protected]
>> 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, [email protected]
>>> 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 <[email protected]>
>>>>> 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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
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 [email protected].
For more options, visit https://groups.google.com/d/optout.