Re: Composing Stuart Sierra's components

2015-04-16 Thread Dan Kee
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

Re: Composing Stuart Sierra's components

2015-04-15 Thread Dan Kee
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