diff --git a/README.md b/README.md index 9a6b804..f0e3614 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt - [Using State](#using-state) - [Dependencies](#dependencies) - [Talking States](#talking-states) + - [Automatically Starting Dependencies](#automatically-starting-dependencies) - [Value of Values](#value-of-values) - [The Importance of Being Reloadable](#the-importance-of-being-reloadable) - [Start and Stop Order](#start-and-stop-order) @@ -163,6 +164,20 @@ this `config`, being top level, can be used in other namespaces, including the o [here](dev/clj/app/www.clj#L32) is an example of a web server that "depends" on a similar `config`. +### Automatically Starting Dependencies + +It is also possible to start the dependencies of a state, when a particular state is started. We can modify the example above as follows + +```clojure +(ns app.database + (:require [mount.core :refer [defstate]] + [app.config :refer [config]])) + +(defstate conn :deps [#'config] :start (create-connection config)) + +(mount/start #'conn) ;; => {:started ["#'app.config/config" "#'app.database/conn"]} +``` + ## Value of values Lifecycle functions start/stop can take both functions and values. This is "valuable" and also works: diff --git a/src/mount/core.cljc b/src/mount/core.cljc index e1d57d4..c90c062 100644 --- a/src/mount/core.cljc +++ b/src/mount/core.cljc @@ -135,14 +135,15 @@ #?(:clj (defmacro defstate [state & body] (let [[state params] (macro/name-with-attributes state body) - {:keys [start stop] :as lifecycle} (apply hash-map params) + {:keys [start stop deps] :as lifecycle} (apply hash-map params) state-name (with-ns *ns* state) order (make-state-seq state-name)] (validate lifecycle) (let [s-meta (cond-> {:order order :start `(fn [] ~start) :status #{:stopped}} - stop (assoc :stop `(fn [] ~stop)))] + stop (assoc :stop `(fn [] ~stop)) + deps (assoc :deps deps))] `(do ;; (log (str "|| mounting... " ~state-name)) (~'defonce ~state (DerefableState. ~state-name)) @@ -241,13 +242,22 @@ (defn- all-without-subs [] (remove (comp :sub? @meta-state) (find-all-states))) +(defn- deps-chain [states] + (when (seq states) + (into (set states) + (->> states + (map var-to-str) + (map @meta-state) + (mapcat :deps) + deps-chain)))) + (defn start [& states] (let [fs (-> states first)] (if (coll? fs) (if-not (empty? fs) ;; (mount/start) vs. (mount/start #{}) vs. (mount/start #{1 2 3}) (apply start fs) {:started #{}}) - (let [states (or (seq states) + (let [states (or (deps-chain states) (all-without-subs))] {:started (bring states up <)})))) diff --git a/test/core/mount/test/start_dependencies.cljc b/test/core/mount/test/start_dependencies.cljc new file mode 100644 index 0000000..2aa7b86 --- /dev/null +++ b/test/core/mount/test/start_dependencies.cljc @@ -0,0 +1,32 @@ +(ns core.mount.test.start-dependencies + (:require + #?@(:cljs [[cljs.test :as t :refer-macros [is deftest use-fixtures]] + [mount.core :as mount :refer-macros [defstate]]] + :clj [[clojure.test :as t :refer [is deftest use-fixtures]] + [mount.core :as mount :refer [defstate]]]) + [mount.test.helper :refer [dval]])) + +#?(:clj (alter-meta! *ns* assoc ::load false)) + +(defstate dependency-1 :start 1) +(defstate dependency-2 :start 2) +(defstate thing-to-start + :deps [#'dependency-1 #'dependency-2] + :start 3) +(defstate should-not-start :start 4) + +(defn- start-states [] + (mount/start #'dependency-1)) + +(use-fixtures :once + #?(:cljs {:before start-states + :after mount/stop} + :clj #((start-states) (%) (mount/stop)))) + +(deftest dependencies-test + (is (= {:started ["#'core.mount.test.start-dependencies/dependency-2" "#'core.mount.test.start-dependencies/thing-to-start"]} + (mount/start #'thing-to-start))) + (is (= 3 (dval thing-to-start))) + (is (= 2 (dval dependency-2))) + (is (= 1 (dval dependency-1))) + (is (instance? mount.core.NotStartedState (dval should-not-start))))