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?