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.