From 46b4895d95e3f08165e37da49f9d1092be933c88 Mon Sep 17 00:00:00 2001 From: anatoly Date: Sun, 31 Jan 2016 15:50:28 -0500 Subject: [PATCH 01/12] [changelog]: updating 0.1.9 release date --- CHANGELOG.md | 1 + project.clj | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46fee0c..e7566ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ ## 0.1.9 +###### Sun Jan 31 15:47:19 2016 -0500 * `:on-reload` #{:noop :stop :restart} ([#36](https://github.com/tolitius/mount/issues/36)) * swapping states with values ([#45](https://github.com/tolitius/mount/issues/45)) diff --git a/project.clj b/project.clj index 2a6b61b..d8486a7 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject mount "0.1.9-SNAPSHOT" +(defproject mount "0.1.9" :description "managing Clojure and ClojureScript app state since (reset)" :url "https://github.com/tolitius/mount" :license {:name "Eclipse Public License" From 346a5b7f8c58efa28477a0048c83de4cb14a53da Mon Sep 17 00:00:00 2001 From: anatoly Date: Sun, 31 Jan 2016 15:51:52 -0500 Subject: [PATCH 02/12] onto 0.1.10-SNAPSHOT --- README.md | 2 +- build.boot | 2 +- project.clj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 782d64a..0ba9502 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt module | branch | status ----------|----------|---------- mount | `master` | [![Circle CI](https://circleci.com/gh/tolitius/mount/tree/master.png?style=svg)](https://circleci.com/gh/tolitius/mount/tree/master) - mount | `0.1.9-SNAPSHOT` | [![Circle CI](https://circleci.com/gh/tolitius/mount/tree/0.1.9-SNAPSHOT.png?style=svg)](https://circleci.com/gh/tolitius/mount/tree/0.1.9-SNAPSHOT) + mount | `0.1.10-SNAPSHOT` | [![Circle CI](https://circleci.com/gh/tolitius/mount/tree/0.1.10-SNAPSHOT.png?style=svg)](https://circleci.com/gh/tolitius/mount/tree/0.1.10-SNAPSHOT) [![Clojars Project](http://clojars.org/mount/latest-version.svg)](http://clojars.org/mount) diff --git a/build.boot b/build.boot index b6ca2aa..b6eff94 100644 --- a/build.boot +++ b/build.boot @@ -1,4 +1,4 @@ -(def +version+ "0.1.9") +(def +version+ "0.1.10-SNAPSHOT") (set-env! :source-paths #{"src"} diff --git a/project.clj b/project.clj index d8486a7..460a102 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject mount "0.1.9" +(defproject mount "0.1.10-SNAPSHOT" :description "managing Clojure and ClojureScript app state since (reset)" :url "https://github.com/tolitius/mount" :license {:name "Eclipse Public License" From 4787ae12f5ecf58a6b82514cf4bd344d5d20f253 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Sun, 31 Jan 2016 23:54:47 -0500 Subject: [PATCH 03/12] [component diff]: note about Yurt --- doc/differences-from-component.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/differences-from-component.md b/doc/differences-from-component.md index 79ebe42..6d27742 100644 --- a/doc/differences-from-component.md +++ b/doc/differences-from-component.md @@ -199,11 +199,11 @@ Testing is not alien to Mount and it knows how to do a thing or two: * [stop an application except certain states](https://github.com/tolitius/mount#stop-an-application-except-certain-states) * [suspending and resuming](https://github.com/tolitius/mount#suspending-and-resuming) -But running two apps in the same JVM side by side with "same but different" states, is not something Mount can do at the moment. +After [booting mount](http://www.dotkam.com/2015/12/22/the-story-of-booting-mount/) I was secretly thinking of achieving multiple separate systems by running them in different [Boot Pods](https://github.com/boot-clj/boot/wiki/Pods). -After [booting mount](http://www.dotkam.com/2015/12/22/the-story-of-booting-mount/) I am secretly thinking of achieving multiple separate systems by running them in different [Boot Pods](https://github.com/boot-clj/boot/wiki/Pods), but for now it remains to be a secret hypothesis. +But the more I think about it, the less it feels like a mount's core functionality. So I created [Yurt](https://github.com/tolitius/yurt) that can easily create and run multiple separate mount systems simultaniously. -###### _conclusion: needs more thinking._ +###### _conclusion: can be done with mount as well, but via a different dependency._ ### Visualizing dependency graph From dc91a44f7215e4304f326c721304c938dd3e7fe6 Mon Sep 17 00:00:00 2001 From: anatoly Date: Wed, 3 Feb 2016 23:20:54 -0500 Subject: [PATCH 04/12] #46: removing :suspend and :resume... [done] --- README.md | 102 +---------- dev/clj/app/utils/logging.clj | 4 +- doc/differences-from-component.md | 1 - src/mount/core.cljc | 48 ++--- test/clj/tapp/utils/logging.clj | 4 +- test/core/mount/test.cljc | 2 - test/core/mount/test/suspend_resume.cljc | 212 ----------------------- 7 files changed, 21 insertions(+), 352 deletions(-) delete mode 100644 test/core/mount/test/suspend_resume.cljc diff --git a/README.md b/README.md index 0ba9502..684f239 100644 --- a/README.md +++ b/README.md @@ -35,10 +35,6 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt - [Swapping Alternate Implementations](#swapping-alternate-implementations) - [Swapping States with Values](#swapping-states-with-values) - [Swapping States with States](#swapping-states-with-states) -- [Suspending and Resuming](#suspending-and-resuming) - - [Suspendable Lifecycle](#suspendable-lifecycle) - - [Plugging into (reset)](#plugging-into-reset) - - [Suspendable Example Application](#suspendable-example-application) - [ClojureScript is Clojure](doc/clojurescript.md#managing-state-in-clojurescript) - [Packaging](#packaging) - [Affected States](#affected-states) @@ -168,7 +164,7 @@ is an example of a web server that "depends" on a similar `config`. ## Value of values -Lifecycle functions start/stop/suspend/resume can take both functions and values. This is "valuable" and also works: +Lifecycle functions start/stop can take both functions and values. This is "valuable" and also works: ```clojure (defstate answer-to-the-ultimate-question-of-life-the-universe-and-everything :start 42) @@ -263,7 +259,7 @@ You can see examples of start and stop flows in the [example app](README.md#moun In REPL or during testing it is often very useful to work with / start / stop _only a part_ of an application, i.e. "only these two states". -`mount`'s lifecycle functions, i.e. start/stop/suspend/resume, can _optionally_ take states as vars (i.e. prefixed with their namespaces): +`mount`'s lifecycle functions, i.e. start/stop, can _optionally_ take states as vars (i.e. prefixed with their namespaces): ```clojure (mount/start #'app.config/config #'app.nyse/conn) @@ -378,80 +374,6 @@ dev=> (mount/start) Notice that the `nyse-app` is not started the second time (hence no more accidental `java.net.BindException: Address already in use`). It is already up and running. -## Suspending and Resuming - -Besides starting and stopping states can also be suspended and resumed. While this is not needed most of the time, it does comes really handy _when_ this need is there. For example: - -* while working in REPL, you only want to truly restart a web server/queue listener/db connection _iff_ something changed, all other times `(mount/stop)` / `(mount/start)` or `(reset)` is called, these states should not be restarted. This might have to do with time to connect / bound ports / connection timeouts, etc.. - -* when taking an application out of rotation in a data center, and then phasing it back in, it might be handy to still keep it _up_, but suspend all the client / novelty facing components in between. - -and some other use cases. - -### Suspendable Lifecycle - -In addition to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones: - -```clojure -(defstate web-server :start start-server - :resume resume-server - :stop stop-server) - -``` - -`suspend` function is optional. Combining this with [(mount/stop-except)](#stop-an-application-except-certain-states), can result in an interesting restart behavior where everything is restared, but this `web-server` is _resumed_ instead (in this case `#'app.www/nyse-app` is an example of the above `web-server`): - -```clojure -dev=> (mount/stop-except #'app.www/nyse-app) -14:44:33.991 [nREPL-worker-1] INFO mount.core - << stopping.. nrepl -14:44:33.992 [nREPL-worker-1] INFO mount.core - << stopping.. conn -14:44:33.992 [nREPL-worker-1] INFO app.db - disconnecting from datomic:mem://mount -14:44:33.992 [nREPL-worker-1] INFO mount.core - << stopping.. config -:stopped -dev=> - -dev=> (mount/suspend) -14:44:52.467 [nREPL-worker-1] INFO mount.core - >> suspending.. nyse-app -:suspended -dev=> - -dev=> (mount/start) -14:45:00.297 [nREPL-worker-1] INFO mount.core - >> starting.. config -14:45:00.297 [nREPL-worker-1] INFO mount.core - >> starting.. conn -14:45:00.298 [nREPL-worker-1] INFO app.db - creating a connection to datomic: datomic:mem://mount -14:45:00.315 [nREPL-worker-1] INFO mount.core - >> resuming.. nyse-app -14:45:00.316 [nREPL-worker-1] INFO mount.core - >> starting.. nrepl -:started -``` - -Notice `>> resuming.. nyse-app`, which in [this case](https://github.com/tolitius/mount/blob/suspendable/test/app/www.clj#L32) just recreates Datomic schema vs. doing that _and_ starting the actual web server. - -### Plugging into (reset) - -In case `tools.namespace` is used, this lifecycle can be easily hooked up with `dev.clj`: - -```clojure -(defn start [] - (mount/start)) - -(defn stop [] - (mount/suspend) - (mount/stop-except #'app.www/nyse-app)) - -(defn reset [] - (stop) - (tn/refresh :after 'dev/start)) -``` - -### Suspendable Example Application - -An [example application](https://github.com/tolitius/mount/tree/suspendable/test/app) with a suspendable web server and `dev.clj` lives in the `suspendable` branch. You can clone mount and try it out: - -``` -$ git checkout suspendable -Switched to branch 'suspendable' -``` - ## Recompiling Namespaces with Running States Mount will detect when a namespace with states (i.e. with `(defstate ...)`) was reloaded/recompiled, @@ -566,28 +488,22 @@ In practice only a few namespaces need to be `:require`d, since others will be b ## Affected States -Every time a lifecycle function (start/stop/suspend/resume) is called mount will return all the states that were affected: +Every time a lifecycle function (start/stop) is called mount will return all the states that were affected: ```clojure dev=> (mount/start) {:started [#'app.config/config #'app.nyse/conn - #'app/nrepl - #'check.suspend-resume-test/web-server - #'check.suspend-resume-test/q-listener]} + #'app/nrepl]} ``` ```clojure -dev=> (mount/suspend) -{:suspended [#'check.suspend-resume-test/web-server - #'check.suspend-resume-test/q-listener]} -``` -```clojure -dev=> (mount/start) -{:started [#'check.suspend-resume-test/web-server - #'check.suspend-resume-test/q-listener]} +dev=> (mount/stop) +{:started [#'app/nrepl + #'app.nyse/conn + #'app.config/config]} ``` -An interesting bit here is a vector vs. a set: all the states are returned _in the order they were changed_. +An interesting bit here is a vector vs. a set: all the states are returned _in the order they were affected_. ## Logging diff --git a/dev/clj/app/utils/logging.clj b/dev/clj/app/utils/logging.clj index 2d8c64d..df58144 100644 --- a/dev/clj/app/utils/logging.clj +++ b/dev/clj/app/utils/logging.clj @@ -34,9 +34,7 @@ (defonce lifecycle-fns #{#'mount.core/up - #'mount.core/down - #'mount.core/sigstop - #'mount.core/sigcont}) + #'mount.core/down}) (defn without-logging-status [] (doall (map #(clear-hooks %) lifecycle-fns))) diff --git a/doc/differences-from-component.md b/doc/differences-from-component.md index 6d27742..1053ef3 100644 --- a/doc/differences-from-component.md +++ b/doc/differences-from-component.md @@ -197,7 +197,6 @@ Testing is not alien to Mount and it knows how to do a thing or two: * [start an application without certain states](https://github.com/tolitius/mount#start-an-application-without-certain-states) * [swapping alternate implementations](https://github.com/tolitius/mount#swapping-alternate-implementations) * [stop an application except certain states](https://github.com/tolitius/mount#stop-an-application-except-certain-states) -* [suspending and resuming](https://github.com/tolitius/mount#suspending-and-resuming) After [booting mount](http://www.dotkam.com/2015/12/22/the-story-of-booting-mount/) I was secretly thinking of achieving multiple separate systems by running them in different [Boot Pods](https://github.com/boot-clj/boot/wiki/Pods). diff --git a/src/mount/core.cljc b/src/mount/core.cljc index b1036d5..95f6816 100644 --- a/src/mount/core.cljc +++ b/src/mount/core.cljc @@ -30,8 +30,7 @@ (defn- validate [{:keys [start stop suspend resume] :as lifecycle}] (cond (not start) (throw-runtime "can't start a stateful thing without a start function. (i.e. missing :start fn)") - (and suspend - (not resume)) (throw-runtime "suspendable state should have a resume function (i.e. missing :resume fn)"))) + (or suspend resume) (throw-runtime "suspend / resume lifecycle support was removed in \"0.1.10\" in favor of (mount/stop-except)"))) (defn- with-ns [ns name] (str "#'" ns "/" name)) @@ -86,18 +85,16 @@ (swap! done conj state-name) state)) -(defn- up [state {:keys [start stop resume status] :as current} done] +(defn- up [state {:keys [start stop status] :as current} done] (when-not (:started status) (let [s (on-error (str "could not start [" state "] due to") - (if (:suspended status) - (record! state resume done) - (record! state start done)))] + (record! state start done))] (alter-state! current s) (swap! running assoc state {:stop stop}) (update-meta! [state :status] #{:started})))) (defn- down [state {:keys [stop status] :as current} done] - (when (some status #{:started :suspended}) + (when (some status #{:started}) (when stop (on-error (str "could not stop [" state "] due to") (record! state stop done))) @@ -105,21 +102,6 @@ (swap! running dissoc state) (update-meta! [state :status] #{:stopped}))) -(defn- sigstop [state {:keys [resume suspend status] :as current} done] - (when (and (:started status) resume) ;; can't have suspend without resume, but the reverse is possible - (when suspend ;; don't suspend if there is only resume function (just mark it :suspended?) - (let [s (on-error (str "could not suspend [" state "] due to") - (record! state suspend done))] - (alter-state! current s))) - (update-meta! [state :status] #{:suspended}))) - -(defn- sigcont [state {:keys [resume status] :as current} done] - (when (:suspended status) - (let [s (on-error (str "could not resume [" state "] due to") - (record! state resume done))] - (alter-state! current s) - (update-meta! [state :status] #{:started})))) - (deftype DerefableState [name] #?(:clj clojure.lang.IDeref :cljs IDeref) @@ -151,16 +133,14 @@ #?(:clj (defmacro defstate [state & body] (let [[state params] (macro/name-with-attributes state body) - {:keys [start stop suspend resume] :as lifecycle} (apply hash-map params) + {:keys [start stop] :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)) - suspend (assoc :suspend `(fn [] ~suspend)) - resume (assoc :resume `(fn [] ~resume)))] + stop (assoc :stop `(fn [] ~stop)))] `(do (~'defonce ~state (DerefableState. ~state-name)) (mount-it (~'var ~state) ~state-name ~s-meta) @@ -226,14 +206,14 @@ (defn- merge-lifecycles "merges with overriding _certain_ non existing keys. - i.e. :suspend is in a 'state', but not in a 'substitute': it should be overriden with nil + i.e. :stop is in a 'state', but not in a 'substitute': it should be overriden with nil however other keys of 'state' (such as :ns,:name,:order) should not be overriden" ([state sub] (merge-lifecycles state nil sub)) - ([state origin {:keys [start stop suspend resume status]}] + ([state origin {:keys [start stop status]}] (assoc state :origin origin :status status - :start start :stop stop :suspend suspend :resume resume))) + :start start :stop stop))) (defn- rollback! [state] (let [{:keys [origin] :as sub} (@meta-state state)] @@ -241,7 +221,7 @@ (update-meta! [state] (merge-lifecycles sub origin))))) (defn- substitute! [state with mode] - (let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :status]) + (let [lifecycle-fns #(select-keys % [:start :stop :status]) origin (@meta-state state) sub (if (= :value mode) {:start (fn [] with) :status :stopped} @@ -299,11 +279,3 @@ without (remove (set states) app)] (apply start without)) (start))) - -(defn suspend [& states] - (let [states (or (seq states) (all-without-subs))] - {:suspended (bring states sigstop <)})) - -(defn resume [& states] - (let [states (or (seq states) (all-without-subs))] - {:resumed (bring states sigcont <)})) diff --git a/test/clj/tapp/utils/logging.clj b/test/clj/tapp/utils/logging.clj index ea7f1ff..516101a 100644 --- a/test/clj/tapp/utils/logging.clj +++ b/test/clj/tapp/utils/logging.clj @@ -34,9 +34,7 @@ (defonce lifecycle-fns #{#'mount.core/up - #'mount.core/down - #'mount.core/sigstop - #'mount.core/sigcont}) + #'mount.core/down}) (defn without-logging-status [] (doall (map clear-hooks lifecycle-fns))) diff --git a/test/core/mount/test.cljc b/test/core/mount/test.cljc index 41158d2..1377e3a 100644 --- a/test/core/mount/test.cljc +++ b/test/core/mount/test.cljc @@ -13,7 +13,6 @@ mount.test.start-without mount.test.start-with mount.test.start-with-states - mount.test.suspend-resume )) #?(:clj (alter-meta! *ns* assoc ::load false)) @@ -32,7 +31,6 @@ 'mount.test.start-without 'mount.test.start-with 'mount.test.start-with-states - 'mount.test.suspend-resume )) (defn run-tests [] diff --git a/test/core/mount/test/suspend_resume.cljc b/test/core/mount/test/suspend_resume.cljc deleted file mode 100644 index c5f5fda..0000000 --- a/test/core/mount/test/suspend_resume.cljc +++ /dev/null @@ -1,212 +0,0 @@ -(ns mount.test.suspend-resume - (:require - #?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]] - [mount.core :as mount :refer-macros [defstate]] - [tapp.websockets :refer [system-a]] - [tapp.conf :refer [config]] - [tapp.audit-log :refer [log]]] - :clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]] - [mount.core :as mount :refer [defstate]] - [tapp.conf :refer [config]] - [tapp.nyse :refer [conn]] - [tapp.example :refer [nrepl]]]) - [mount.test.helper :refer [dval]])) - -#?(:clj (alter-meta! *ns* assoc ::load false)) - -(defn koncat [k s] - (-> (name k) - (str "-" (name s)) - keyword)) - -(defn start [s] (koncat s :started)) -(defn stop [s] (koncat s :stopped)) -(defn suspend [s] (koncat s :suspended)) -(defn resume [s] (koncat s :resumed)) - -(defstate web-server :start (start :w) - :stop (stop :w) - :suspend (suspend :w) - :resume (resume :w)) - -(defstate q-listener :start (start :q) - :stop (stop :q) - :suspend (suspend :q) - :resume (resume :q)) - -(defstate randomizer :start (rand-int 42)) - -#?(:cljs - (deftest suspendable-lifecycle - - (testing "should suspend _only suspendable_ states that are currently started" - (let [_ (mount/start) - _ (mount/suspend)] - (is (map? (dval config))) - (is (instance? datascript.db/DB @(dval log))) - (is (instance? js/WebSocket (dval system-a))) - (is (= (dval web-server) :w-suspended)) - (mount/stop))) - - (testing "should resume _only suspendable_ states that are currently suspended" - (let [_ (mount/start) - _ (mount/stop #'tapp.websockets/system-a) - _ (mount/suspend) - _ (mount/resume)] - (is (map? (dval config))) - (is (instance? mount.core.NotStartedState (dval system-a))) - (is (instance? datascript.db/DB @(dval log))) - (is (= (dval web-server) :w-resumed)) - (mount/stop))) - - (testing "should start all the states, except the ones that are currently suspended, should resume them instead" - (let [_ (mount/start) - _ (mount/suspend) - _ (mount/start)] - (is (map? (dval config))) - (is (instance? js/WebSocket (dval system-a))) - (is (instance? datascript.db/DB @(dval log))) - (is (= (dval web-server) :w-resumed)) - (mount/stop))) - - (testing "should stop all: started and suspended" - (let [_ (mount/start) - _ (mount/suspend) - _ (mount/stop)] - (is (instance? mount.core.NotStartedState (dval config))) - (is (instance? mount.core.NotStartedState (dval system-a))) - (is (instance? mount.core.NotStartedState (dval log))) - (is (instance? mount.core.NotStartedState (dval web-server))))))) - -#?(:cljs - (deftest suspendable-start-with-states - - (testing "when replacing a non suspendable state with a suspendable one, - the later should be able to suspend/resume, - the original should not be suspendable after resume and preserve its lifecycle fns after rollback/stop" - (let [_ (mount/start-with-states {#'tapp.websockets/system-a #'mount.test.suspend-resume/web-server}) - _ (mount/suspend)] - (is (= (dval system-a) :w-suspended)) - (is (instance? mount.core.NotStartedState (dval web-server))) - (mount/stop) - (mount/start) - (mount/suspend) - (is (instance? js/WebSocket (dval system-a))) - (is (= (dval web-server) :w-suspended)) - (mount/stop))))) - -#?(:clj - (deftest suspendable-lifecycle - - (testing "should suspend _only suspendable_ states that are currently started" - (let [_ (mount/start) - _ (mount/suspend)] - (is (map? (dval config))) - (is (instance? clojure.tools.nrepl.server.Server (dval nrepl))) - (is (instance? datomic.peer.LocalConnection (dval conn))) - (is (= (dval web-server) :w-suspended)) - (mount/stop))) - - (testing "should resume _only suspendable_ states that are currently suspended" - (let [_ (mount/start) - _ (mount/stop #'tapp.example/nrepl) - _ (mount/suspend) - _ (mount/resume)] - (is (map? (dval config))) - (is (instance? mount.core.NotStartedState (dval nrepl))) - (is (instance? datomic.peer.LocalConnection (dval conn))) - (is (= (dval web-server) :w-resumed)) - (mount/stop))) - - (testing "should start all the states, except the ones that are currently suspended, should resume them instead" - (let [_ (mount/start) - _ (mount/suspend) - _ (mount/start)] - (is (map? (dval config))) - (is (instance? clojure.tools.nrepl.server.Server (dval nrepl))) - (is (instance? datomic.peer.LocalConnection (dval conn))) - (is (= (dval web-server) :w-resumed)) - (mount/stop))) - - (testing "should stop all: started and suspended" - (let [_ (mount/start) - _ (mount/suspend) - _ (mount/stop)] - (is (instance? mount.core.NotStartedState (dval config))) - (is (instance? mount.core.NotStartedState (dval nrepl))) - (is (instance? mount.core.NotStartedState (dval conn))) - (is (instance? mount.core.NotStartedState (dval web-server))))))) - - -#?(:clj - (deftest suspendable-start-with-states - - (testing "when replacing a non suspendable state with a suspendable one, - the later should be able to suspend/resume, - the original should not be suspendable after resume and preserve its lifecycle fns after rollback/stop" - (let [_ (mount/start-with-states {#'tapp.example/nrepl #'mount.test.suspend-resume/web-server}) - _ (mount/suspend)] - (is (= (dval nrepl) :w-suspended)) - (is (instance? mount.core.NotStartedState (dval web-server))) - (mount/stop) - (mount/start) - (mount/suspend) - (is (instance? clojure.tools.nrepl.server.Server (dval nrepl))) - (is (= (dval web-server) :w-suspended)) - (mount/stop))) - - ;; this is a messy use case, but can still happen especially at REPL time - ;; it also messy, because usually :stop function refers the _original_ state by name (i.e. #(disconnect conn)) - ;; (unchanged/not substituted in its lexical scope), and original state won't be started - (testing "when replacing a suspendable state with a non suspendable one, - the later should not be suspendable, - the original should still be suspendable and preserve its lifecycle fns after the rollback/stop" - (let [_ (mount/start-with-states {#'mount.test.suspend-resume/web-server #'mount.test.suspend-resume/randomizer}) - _ (mount/suspend)] - (is (integer? (dval web-server))) - (is (instance? mount.core.NotStartedState (dval randomizer))) - (mount/stop) - (mount/start) - (mount/suspend) - (is (integer? (dval randomizer))) - (is (= (dval web-server) :w-suspended)) - (mount/stop))) - - ;; this is a messy use case, but can still happen especially at REPL time - (testing "when replacing a suspended state with a non suspendable started one, - the later should not be suspendable, - the original should still be suspended and preserve its lifecycle fns after the rollback/stop" - (let [_ (mount/start) - _ (mount/suspend) - _ (mount/start-with-states {#'mount.test.suspend-resume/web-server #'tapp.nyse/conn}) ;; TODO: good to WARN on started states during "start-with-states" - _ (mount/suspend)] - (is (instance? datomic.peer.LocalConnection (dval conn))) - (is (= (dval web-server) :w-suspended)) ;; since the "conn" does not have a resume method, so web-server was not started - (mount/stop) - (mount/start) - (mount/suspend) - (is (instance? datomic.peer.LocalConnection (dval conn))) - (is (= (dval web-server) :w-suspended)) - (mount/stop))) - - ;; this is a messy use case, but can still happen especially at REPL time - (testing "when replacing a suspended state with a suspendable one, - the later should be suspendable, - the original should still be suspended and preserve its lifecycle fns after the rollback/stop" - (let [_ (mount/start) - _ (mount/suspend) - _ (mount/start-with-states {#'mount.test.suspend-resume/web-server - #'mount.test.suspend-resume/q-listener})] ;; TODO: good to WARN on started states during "start-with-states" - (is (= (dval q-listener) :q-suspended)) - (is (= (dval web-server) :q-resumed)) - (mount/suspend) - (is (= (dval q-listener) :q-suspended)) - (is (= (dval web-server) :q-suspended)) - (mount/stop) - (is (instance? mount.core.NotStartedState (dval web-server))) - (is (instance? mount.core.NotStartedState (dval q-listener))) - (mount/start) - (mount/suspend) - (is (= (dval q-listener) :q-suspended)) - (is (= (dval web-server) :w-suspended)) - (mount/stop))))) From 98496d63d80f9a5b055b75554dfc77a699cfbcc7 Mon Sep 17 00:00:00 2001 From: anatoly Date: Mon, 22 Feb 2016 12:15:19 -0500 Subject: [PATCH 05/12] #47 alpha draft for composable states --- src/mount/core.cljc | 48 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/mount/core.cljc b/src/mount/core.cljc index 95f6816..ea125cc 100644 --- a/src/mount/core.cljc +++ b/src/mount/core.cljc @@ -248,6 +248,54 @@ (dorun (map rollback! states)) ;; restore to origin from "start-with" {:stopped stopped})) +;; composable set of states + +(defn only + ([states] + states) + ([states these] + (filter these states))) + +(defn with-args + ([args] + (with-args (find-all-states) args)) + ([states args] + (reset! -args args) ;; TODO localize + states)) + +(defn except + ([states] + (except (find-all-states) states)) + ([states these] + (remove these states))) + +(defn swap + ([with] + (swap (find-all-states) with)) + ([states with] + (doseq [[from to] with] + (substitute! (var-to-str from) + to :value)) + states)) + +(defn swap-states + ([with] + (swap-states (find-all-states) with)) + ([states with] + (doseq [[from to] with] + (substitute! (var-to-str from) + (var-to-str to) :state)) + states)) + +#_(-> (only #{1 2 3 4}) + (with-args {}) + (except #{2 1}) + (swap {2 42 1 34}) + (swap-states {4 "#'foo.bar/42"}) + (start)) + +;; explicit, not composable (subject to depreciate?) + (defn stop-except [& states] (let [all (set (find-all-states)) states (map var-to-str states) From b77bef3457a4db8ddeb8a447a487eb8943cdd0bf Mon Sep 17 00:00:00 2001 From: anatoly Date: Mon, 22 Feb 2016 16:48:05 -0500 Subject: [PATCH 06/12] #47 except, with-args, only, swap tests.. more to come --- src/mount/core.cljc | 20 +++--- test/core/mount/test/composable_fns.cljc | 90 ++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 test/core/mount/test/composable_fns.cljc diff --git a/src/mount/core.cljc b/src/mount/core.cljc index ea125cc..95549dd 100644 --- a/src/mount/core.cljc +++ b/src/mount/core.cljc @@ -1,6 +1,7 @@ (ns mount.core #?(:clj (:require [mount.tools.macro :refer [on-error throw-runtime] :as macro] [mount.tools.logger :refer [log]] + [clojure.set :refer [intersection]] [clojure.string :as s]) :cljs (:require [mount.tools.macro :as macro] [mount.tools.logger :refer [log]])) @@ -250,11 +251,16 @@ ;; composable set of states +(defn- mapset [f xs] + (-> (map f xs) + set)) + (defn only ([states] - states) + (only (find-all-states) states)) ([states these] - (filter these states))) + (intersection (mapset var-to-str these) + (mapset var-to-str states)))) (defn with-args ([args] @@ -267,7 +273,8 @@ ([states] (except (find-all-states) states)) ([states these] - (remove these states))) + (remove (mapset var-to-str these) + (mapset var-to-str states)))) (defn swap ([with] @@ -287,13 +294,6 @@ (var-to-str to) :state)) states)) -#_(-> (only #{1 2 3 4}) - (with-args {}) - (except #{2 1}) - (swap {2 42 1 34}) - (swap-states {4 "#'foo.bar/42"}) - (start)) - ;; explicit, not composable (subject to depreciate?) (defn stop-except [& states] diff --git a/test/core/mount/test/composable_fns.cljc b/test/core/mount/test/composable_fns.cljc new file mode 100644 index 0000000..43703a0 --- /dev/null +++ b/test/core/mount/test/composable_fns.cljc @@ -0,0 +1,90 @@ +(ns mount.test.composable-fns + (:require + #?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]] + [clojure.set :refer [intersection]] + [mount.core :refer [only except swap swap-states with-args] :as mount :refer-macros [defstate]] + [tapp.websockets :refer [system-a]] + [tapp.conf :refer [config]] + [tapp.audit-log :refer [log]]] + :clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]] + [clojure.set :refer [intersection]] + [mount.core :as mount :refer [defstate only except swap swap-states with-args]] + [tapp.conf :refer [config]] + [tapp.nyse :refer [conn]] + [tapp.example :refer [nrepl]]]) + [mount.test.helper :refer [dval helper]])) + +#?(:clj (alter-meta! *ns* assoc ::load false)) + +(defstate test-conn :start 42 + :stop (constantly 0)) + +(defstate test-nrepl :start []) + +#?(:clj + (deftest only-states + + (testing "only should only return given states. + if source set of states is not provided, it should use all the states to select from" + (is (= #{"#'mount.test.composable-fns/test-conn" "#'tapp.example/nrepl" "#'tapp.nyse/conn"} + (only #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn})))) + + (testing "only should only return given states" + (is (= #{"#'mount.test.composable-fns/test-conn" "#'tapp.example/nrepl"} + (only [#'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn] + #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl})))))) + +#?(:clj + (deftest except-states + + (testing "except should exclude given states. + if source set of states is not provided, it should use all the states to exclude from" + (let [states (except #{"#'is.not/here" #'tapp.example/nrepl #'tapp.nyse/conn})] + (is (coll? states)) + (is (pos? (count states))) + (is (zero? (count (intersection (set states) + #{"#'tapp.example/nrepl" "#'tapp.nyse/conn" "#'is.not/here"})))))) + + (testing "except should exclude given states" + (is (= #{"#'tapp.conf/config" "#'mount.test.composable-fns/test-conn"} + (set (except #{#'tapp.example/nrepl #'tapp.conf/config #'mount.test.composable-fns/test-conn} + #{"#'is.not/here" #'tapp.example/nrepl #'tapp.nyse/conn}))))))) + +#?(:clj + (deftest states-with-args + + (testing "with-args should set args and return all states if none provided" + (let [states (with-args {:a 42})] + (is (= {:a 42} (mount/args))) + (is (= states (#'mount.core/find-all-states))))) + + (testing "with-args should set args and thread states if provided" + (let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn} + states (with-args t-states {:a 42})] + (is (= {:a 42} (mount/args))) + (is (= states t-states)))))) + +#?(:clj + (deftest swap-states-with-values + + (testing "swap should swap states with values and return all states if none is given" + (let [states (swap {#'tapp.nyse/conn "conn-sub" + #'tapp.example/nrepl :nrepl-sub})] + (is (= states (#'mount.core/find-all-states))) + (mount/start) + (is (map? (dval config))) + (is (= :nrepl-sub (dval nrepl))) + (is (= "conn-sub" (dval conn))) + (mount/stop))) + + (testing "swap should swap states with values and return only states that it is given" + (let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn} + states (swap t-states {#'tapp.nyse/conn "conn-sub" + #'tapp.example/nrepl :nrepl-sub})] + (is (= states t-states)) + (mount/start) + (is (map? (dval config))) + (is (= :nrepl-sub (dval nrepl))) + (is (= "conn-sub" (dval conn))) + (is (= 42 (dval test-conn))) + (mount/stop))))) From 977806626d6b3d7c989620b549a161a462f06a82 Mon Sep 17 00:00:00 2001 From: anatoly Date: Mon, 22 Feb 2016 17:26:07 -0500 Subject: [PATCH 07/12] #47 swap-states tests are in. composability tests ...next --- circle.yml | 2 +- test/core/mount/test/composable_fns.cljc | 26 ++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index e8bbe1d..b04a609 100644 --- a/circle.yml +++ b/circle.yml @@ -12,4 +12,4 @@ test: - boot test - boot test-cljs - boot test-cljs-advanced - - lein test2junit + - lein do clean, test2junit diff --git a/test/core/mount/test/composable_fns.cljc b/test/core/mount/test/composable_fns.cljc index 43703a0..326bcab 100644 --- a/test/core/mount/test/composable_fns.cljc +++ b/test/core/mount/test/composable_fns.cljc @@ -88,3 +88,29 @@ (is (= "conn-sub" (dval conn))) (is (= 42 (dval test-conn))) (mount/stop))))) + +#?(:clj + (deftest swap-states-with-states + + (testing "swap-states should swap states with states and return all mount states if none is given" + (let [states (swap-states {#'tapp.nyse/conn #'mount.test.composable-fns/test-conn + #'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})] + (is (= states (#'mount.core/find-all-states))) + (mount/start) + (is (map? (dval config))) + (is (vector? (dval nrepl))) + (is (= 42 (dval conn))) + (mount/stop))) + + (testing "swap-states should swap states with states and return only states that it is given" + (let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.nyse/conn} + states (swap-states t-states {#'tapp.nyse/conn #'mount.test.composable-fns/test-conn + #'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})] + (is (= states t-states)) + (apply mount/start states) + (is (instance? mount.core.NotStartedState (dval config))) + (is (instance? mount.core.NotStartedState (dval nrepl))) + (is (= 42 (dval conn))) + (is (= 42 (dval test-conn))) ;; test-conn is explicitly started via "t-states" + (mount/stop))))) + From c2828cc3dc55cef04fae60b2aec3a8e4e89566f4 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Mon, 22 Feb 2016 22:13:25 -0500 Subject: [PATCH 08/12] [docs]: fixing a link to a server config example --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 684f239..d100df9 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ this `config`, being top level, can be used in other namespaces, including the o (defstate conn :start (create-connection config)) ``` -[here](dev/clj/app/www.clj#L30) +[here](dev/clj/app/www.clj#L32) is an example of a web server that "depends" on a similar `config`. ## Value of values From 926e4a9d2a7df72867b7b439d18c7cc55bfa00da Mon Sep 17 00:00:00 2001 From: Anatoly Date: Sun, 31 Jan 2016 23:54:47 -0500 Subject: [PATCH 09/12] #47 composition tests are in --- src/mount/core.cljc | 7 ++-- test/core/mount/test/composable_fns.cljc | 41 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/mount/core.cljc b/src/mount/core.cljc index 95549dd..ab2598e 100644 --- a/src/mount/core.cljc +++ b/src/mount/core.cljc @@ -4,6 +4,7 @@ [clojure.set :refer [intersection]] [clojure.string :as s]) :cljs (:require [mount.tools.macro :as macro] + [clojure.set :refer [intersection]] [mount.tools.logger :refer [log]])) #?(:cljs (:require-macros [mount.core] [mount.tools.macro :refer [if-clj on-error throw-runtime]]))) @@ -239,8 +240,10 @@ (remove (comp :sub? @meta-state) (find-all-states))) (defn start [& states] - (let [states (or (seq states) (all-without-subs))] - {:started (bring states up <)})) + (if (-> states first coll?) + (apply start (first states)) + (let [states (or (seq states) (all-without-subs))] + {:started (bring states up <)}))) (defn stop [& states] (let [states (or states (find-all-states)) diff --git a/test/core/mount/test/composable_fns.cljc b/test/core/mount/test/composable_fns.cljc index 326bcab..ab7ff5b 100644 --- a/test/core/mount/test/composable_fns.cljc +++ b/test/core/mount/test/composable_fns.cljc @@ -114,3 +114,44 @@ (is (= 42 (dval test-conn))) ;; test-conn is explicitly started via "t-states" (mount/stop))))) +#?(:clj + (deftest composing + + (testing "states provided to the top level should narrow down the scope for the whole composition" + (let [scope [#'tapp.conf/config + #'tapp.example/nrepl + #'tapp.nyse/conn + #'mount.test.composable-fns/test-nrepl + #'mount.test.composable-fns/test-conn] + states (-> (only scope) + (with-args {:a 42}) + (except [#'mount.test.composable-fns/test-nrepl + #'mount.test.composable-fns/test-conn]) + (swap-states {#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl}) + (swap {#'tapp.conf/config {:datomic {:uri "datomic:mem://composable-mount"}}}))] + (is (= #{"#'tapp.nyse/conn" "#'tapp.conf/config" "#'tapp.example/nrepl"} (set states))) + (mount/start states) + (is (= {:a 42} (mount/args))) + (is (= {:datomic {:uri "datomic:mem://composable-mount"}} (dval config))) + (is (instance? datomic.peer.LocalConnection (dval conn))) + (is (vector? (dval nrepl))) + (mount/stop))) + + (testing "should compose and start in a single composition" + (let [scope [#'tapp.conf/config + #'tapp.example/nrepl + #'tapp.nyse/conn + #'mount.test.composable-fns/test-nrepl + #'mount.test.composable-fns/test-conn]] + (-> (only scope) + (with-args {:a 42}) + (except [#'mount.test.composable-fns/test-nrepl + #'mount.test.composable-fns/test-conn]) + (swap-states {#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl}) + (swap {#'tapp.conf/config {:datomic {:uri "datomic:mem://composable-mount"}}}) + mount/start) + (is (= {:a 42} (mount/args))) + (is (= {:datomic {:uri "datomic:mem://composable-mount"}} (dval config))) + (is (instance? datomic.peer.LocalConnection (dval conn))) + (is (vector? (dval nrepl))) + (mount/stop))))) From f801599f68a7cb62feb32aad112709ef0bc8d761 Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 23 Feb 2016 01:24:15 -0500 Subject: [PATCH 10/12] #47 [docs]: composing states --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d100df9..8327bf7 100644 --- a/README.md +++ b/README.md @@ -28,13 +28,14 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt - [Talking States](#talking-states) - [Value of Values](#value-of-values) - [The Importance of Being Reloadable](#the-importance-of-being-reloadable) +- [Composing States](#composing-states) - [Start and Stop Order](#start-and-stop-order) - [Start and Stop Parts of Application](#start-and-stop-parts-of-application) - [Start an Application Without Certain States](#start-an-application-without-certain-states) -- [Stop an Application Except Certain States](#stop-an-application-except-certain-states) - [Swapping Alternate Implementations](#swapping-alternate-implementations) - [Swapping States with Values](#swapping-states-with-values) - [Swapping States with States](#swapping-states-with-states) +- [Stop an Application Except Certain States](#stop-an-application-except-certain-states) - [ClojureScript is Clojure](doc/clojurescript.md#managing-state-in-clojurescript) - [Packaging](#packaging) - [Affected States](#affected-states) @@ -230,6 +231,69 @@ Here is a [dev.clj](dev/clj/dev.clj) as an example, that sums up to: the `(reset)` is then used in REPL to restart / reload application state without the need to restart the REPL itself. +## Composing States + +Besides calling `(mount/start)` there are other useful ways to start an application: + +* [starting parts of an application](README.md#start-and-stop-parts-of-application) +* [starting an application without certain states](README.md#start-an-application-without-certain-states) +* [swapping alternate implementations](README.md#swapping-alternate-implementations) +* [passing runtime arguments](README.md#runtime-arguments) + +While all of these are great by themselves, sometimes it is really handy to compose these super powers. For example to start an application with _only_ certain states, _swapping_ a couple of them for new values, while passing runtime _arguments_. + +### Composer's Toolbox + +Each "tool" has a single responsibility and can be composed with other tools in _any_ combination and order. + +* `only` will return _only_ states that it is given + exist (seen by mount) in the application +* `except` will return all the states that it is given _except_ a given set +* `swap` will take a map with keys as states and values as their substitute values +* `swap-states` will take a map with keys as states and values as their substitute states +* `with-args` will take a map that could later be accessed by `(mount/args)` + +All these functions take one or two arguments. If called with two arguments, the first one will be treated as the universe of states to work with. If called with one argument, it will work with _all known_ to mount states. + +None of these functions start or stop the application states, they merely serve as transformations from the initial set of states to the one that will later be passed to `(mount/start)`. + +### Be Composing + +All of the above is much easier to understand by looking at examples: + +```clojure +(-> (only #{#'foo/a + #'foo/b + #'foo/c + #'bar/d + #'baz/e}) + (except [#'foo/c + #'bar/d]) + (with-args {:a 42}) + mount/start) +``` + +This would start off from 5 states, even though the whole application may have many more states available. It would then exclude two states (i.e. `#'foo/c` and `#'bar/d`), then it will pass runtime arguments `{:a 42}`, and finally it will start the remaining three states: `#'foo/a`, `#'foo/b`, `#'baz/e`. + +You may notice that `only` takes a set, while `except` takes a vector in this example. This is done intentionally to demonstraate that both these functions can take any collection of states. `Set` would make more sense for most cases though. + +Here is a more "involved" example: + +```clojure +(-> (only #{#'foo/a + #'foo/b + #'foo/c + #'bar/d + #'baz/e}) + (with-args {:a 42}) + (except [#'foo/c + #'bar/d]) + (swap-states {#'foo/a #'test/a}) + (swap {#'baz/e {:datomic {:uri "datomic:mem://composable-mount"}}}) + mount/start) +``` + +This will do the same thing as the previous example plus it would swap `#'foo/a` with `#'test/a` state and `#'baz/e` with `{:datomic {:uri "datomic:mem://composable-mount"}}` value before starting the application. + ## Start and Stop Order Since dependencies are "injected" by `require`ing on the namespace level, `mount` **trusts the Clojure compiler** to From a1e8e9c8e0e9da2806b941aae7c73f761592d52f Mon Sep 17 00:00:00 2001 From: Anatoly Date: Tue, 23 Feb 2016 01:33:49 -0500 Subject: [PATCH 11/12] [doc]: moving start/stop order above composing states --- README.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 8327bf7..81b1325 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt - [Talking States](#talking-states) - [Value of Values](#value-of-values) - [The Importance of Being Reloadable](#the-importance-of-being-reloadable) -- [Composing States](#composing-states) - [Start and Stop Order](#start-and-stop-order) +- [Composing States](#composing-states) - [Start and Stop Parts of Application](#start-and-stop-parts-of-application) - [Start an Application Without Certain States](#start-an-application-without-certain-states) - [Swapping Alternate Implementations](#swapping-alternate-implementations) @@ -231,6 +231,31 @@ Here is a [dev.clj](dev/clj/dev.clj) as an example, that sums up to: the `(reset)` is then used in REPL to restart / reload application state without the need to restart the REPL itself. +## Start and Stop Order + +Since dependencies are "injected" by `require`ing on the namespace level, `mount` **trusts the Clojure compiler** to +maintain the start and stop order for all the `defstates`. + +The "start" order is then recorded and replayed on each `(reset)`. + +The "stop" order is simply `(reverse "start order")`: + +```clojure +dev=> (reset) +08:21:39.430 [nREPL-worker-1] DEBUG mount - << stopping.. nrepl +08:21:39.431 [nREPL-worker-1] DEBUG mount - << stopping.. conn +08:21:39.432 [nREPL-worker-1] DEBUG mount - << stopping.. config + +:reloading (app.config app.nyse app.utils.datomic app) + +08:21:39.462 [nREPL-worker-1] DEBUG mount - >> starting.. config +08:21:39.463 [nREPL-worker-1] DEBUG mount - >> starting.. conn +08:21:39.481 [nREPL-worker-1] DEBUG mount - >> starting.. nrepl +:ready +``` + +You can see examples of start and stop flows in the [example app](README.md#mount-and-develop). + ## Composing States Besides calling `(mount/start)` there are other useful ways to start an application: @@ -274,7 +299,7 @@ All of the above is much easier to understand by looking at examples: This would start off from 5 states, even though the whole application may have many more states available. It would then exclude two states (i.e. `#'foo/c` and `#'bar/d`), then it will pass runtime arguments `{:a 42}`, and finally it will start the remaining three states: `#'foo/a`, `#'foo/b`, `#'baz/e`. -You may notice that `only` takes a set, while `except` takes a vector in this example. This is done intentionally to demonstraate that both these functions can take any collection of states. `Set` would make more sense for most cases though. +You may notice that `only` takes a set, while `except` takes a vector in this example. This is done intentionally to demonstraate that both these functions can take any collection of states. `set` would make more sense for most cases though. Here is a more "involved" example: @@ -294,31 +319,6 @@ Here is a more "involved" example: This will do the same thing as the previous example plus it would swap `#'foo/a` with `#'test/a` state and `#'baz/e` with `{:datomic {:uri "datomic:mem://composable-mount"}}` value before starting the application. -## Start and Stop Order - -Since dependencies are "injected" by `require`ing on the namespace level, `mount` **trusts the Clojure compiler** to -maintain the start and stop order for all the `defstates`. - -The "start" order is then recorded and replayed on each `(reset)`. - -The "stop" order is simply `(reverse "start order")`: - -```clojure -dev=> (reset) -08:21:39.430 [nREPL-worker-1] DEBUG mount - << stopping.. nrepl -08:21:39.431 [nREPL-worker-1] DEBUG mount - << stopping.. conn -08:21:39.432 [nREPL-worker-1] DEBUG mount - << stopping.. config - -:reloading (app.config app.nyse app.utils.datomic app) - -08:21:39.462 [nREPL-worker-1] DEBUG mount - >> starting.. config -08:21:39.463 [nREPL-worker-1] DEBUG mount - >> starting.. conn -08:21:39.481 [nREPL-worker-1] DEBUG mount - >> starting.. nrepl -:ready -``` - -You can see examples of start and stop flows in the [example app](README.md#mount-and-develop). - ## Start and Stop Parts of Application In REPL or during testing it is often very useful to work with / start / stop _only a part_ of an application, i.e. "only these two states". From d5eb56c4221b5fddefbe66a12c960b4950c26a6e Mon Sep 17 00:00:00 2001 From: anatoly Date: Sun, 28 Feb 2016 00:02:42 -0500 Subject: [PATCH 12/12] runtime args should init to {} --- src/mount/core.cljc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mount/core.cljc b/src/mount/core.cljc index ab2598e..8d4ea82 100644 --- a/src/mount/core.cljc +++ b/src/mount/core.cljc @@ -9,7 +9,7 @@ #?(:cljs (:require-macros [mount.core] [mount.tools.macro :refer [if-clj on-error throw-runtime]]))) -(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files +(defonce ^:private -args (atom {})) ;; mostly for command line args and external files (defonce ^:private state-seq (atom 0)) (defonce ^:private mode (atom :clj)) (defonce ^:private meta-state (atom {}))