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.

Reply via email to