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.

Reply via email to