diff --git a/README.md b/README.md index 9210d1e..5f03c72 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ But if stateful things are kept lean and low level (i.e. I/O, queues, threads, c ### Talking States -There are of course direct dependecies that `mount` respects: +There are of course direct dependencies that `mount` respects: ```clojure (ns app.config @@ -159,8 +159,8 @@ this `config`, being top level, can be used in other namespaces, including the o (defstate conn :start (create-connection config)) ``` -[here](dev/clj/app/nyse.clj) -is an example of a Datomic connection that "depends" on a similar `config`. +[here](dev/clj/app/www.clj#L30) +is an example of a web server that "depends" on a similar `config`. ## Value of values @@ -203,7 +203,7 @@ Check out [fun-with-values-test](test/core/mount/test/fun_with_values.cljc) for ## The Importance of Being Reloadable `mount` has start and stop functions that will walk all the states created with `defstate` and start / stop them -accordingly: i.e. will call their `:start` and `:stop` defined functions. Hence the whole applicatoin state can be reloaded in REPL e.g.: +accordingly: i.e. will call their `:start` and `:stop` defined functions. Hence the whole application state can be reloaded in REPL e.g.: ``` dev=> (require '[mount.core :as mount]) @@ -212,7 +212,7 @@ dev=> (mount/stop) dev=> (mount/start) ``` -While it is not always necessary, mount lificycle can be easily hooked up to [tools.namespace](https://github.com/clojure/tools.namespace), +While it is not always necessary, mount lifecycle can be easily hooked up to [tools.namespace](https://github.com/clojure/tools.namespace), to make the whole application reloadable with refreshing the app namespaces. Here is a [dev.clj](dev/clj/dev.clj) as an example, that sums up to: @@ -286,7 +286,7 @@ Here is an [example](test/core/mount/test/start_without.cljc) test that excludes ## Swapping Alternate Implementations -During testing it is often very useful to mock/stub certain states. For example runnig a test against an in memory database vs. the real one, running with a publisher that publishes to a test core.async channel vs. the real remote queue, etc. +During testing it is often very useful to mock/stub certain states. For example running a test against an in memory database vs. the real one, running with a publisher that publishes to a test core.async channel vs. the real remote queue, etc. The `start-with` function can do just that: @@ -355,7 +355,7 @@ and some other use cases. ### Suspendable Lifecycle -In additiong to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones: +In addition to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones: ```clojure (defstate web-server :start start-server @@ -500,7 +500,7 @@ The way this is done is via an excellent [robert hooke](https://github.com/techn ## Clojure Version -Since mount [supports both](doc/clojurescript.md#managing-state-in-clojurescript) Clojure and CljoureScript, it relies on [Reader Conditionals](http://clojure.org/reader#The%20Reader--Reader%20Conditionals) that were introduced in `Clojure 1.7`. mount's code is not precompiled (i.e. AOT) and disributed in `.cljc` sources, hence it currently requires `Clojure 1.7` and above. +Since mount [supports both](doc/clojurescript.md#managing-state-in-clojurescript) Clojure and CljoureScript, it relies on [Reader Conditionals](http://clojure.org/reader#The%20Reader--Reader%20Conditionals) that were introduced in `Clojure 1.7`. mount's code is not precompiled (i.e. AOT) and distributed in `.cljc` sources, hence it currently requires `Clojure 1.7` and above. ## Mount and Develop! @@ -609,7 +609,7 @@ dev=> (find-orders "TSLA") ### New York Stock Exchange Maintenance -Say we want to leave the exchage functioning, but would like to make sure that noone can hit it from the web. Easy, just stop the web server: +Say we want to leave the exchange functioning, but would like to make sure that noone can hit it from the web. Easy, just stop the web server: ```clojure dev=> (mount/stop #'app.www/nyse-app) diff --git a/doc/clojurescript.md b/doc/clojurescript.md index c842882..480b042 100644 --- a/doc/clojurescript.md +++ b/doc/clojurescript.md @@ -22,7 +22,7 @@ that most of mount code lives in `.cljc` files. The way mount is designed it "mounts" itself to a solid Clojure [namespace API](http://clojure.org/namespaces), and while `.cljc` helps a lot with targeting Clojure and ClojureScript, JavaScript VM is vastly different from JVM. Since JavaScript mostly tagrets browsers, mobile devices and IoT, -it is quite importand to [compress](https://github.com/clojure/clojurescript/wiki/Advanced-Compilation) the final result. +it is quite important to [compress](https://github.com/clojure/clojurescript/wiki/Advanced-Compilation) the final result. Which means that Clojure namespaces API are not that well supported in ClojureScript, since they get renamed and optimized during compilation + of course no native namespace support on the JavaScript side @@ -53,17 +53,17 @@ from the default `clj` mode: > all states are "_derefable_" -which means in order to use them, you'd need to `@` it. That's where the difference between two modes end. +which means in order to use them, you'd need to `@` it. That's where the difference between the two modes ends. Again, `cljc` mode API is _consistent across both_ Clojure and ClojureScript. While initially it may sound strange, this approach has very nice properties: -* Mentally something that you defer (`@`) is associated with a state behind it +* Mentally something that you deref (`@`) is associated with a state behind it * The whole system may start lazily without an explicit call `(mount/start)` * States may have watchers which is just an idea at this point, but it could be quite useful -Now as the theory is laid out... +Now that the theory is laid out... ### Mounting that ClojureScript diff --git a/doc/differences-from-component.md b/doc/differences-from-component.md index 430e2c2..79ebe42 100644 --- a/doc/differences-from-component.md +++ b/doc/differences-from-component.md @@ -22,13 +22,14 @@ The not so hidden benefit is REPL time reloadability that it brings to the table - [Then why "mount"!?](#then-why-mount) - [So what are the differences?](#so-what-are-the-differences) - - [Objects vs. Namespaces](#objects-vs-namespaces) - - [Start and Stop Order](#start-and-stop-order) - [Component requires whole app buy in](#component-requires-whole-app-buy-in) + - [Start and Stop Order](#start-and-stop-order) - [Refactoring an existing application](#refactoring-an-existing-application) - [Code navigation](#code-navigation) + - [Objects vs. Namespaces](#objects-vs-namespaces) - [Starting and stopping parts of an application](#starting-and-stopping-parts-of-an-application) - [Boilerplate code](#boilerplate-code) + - [Library vs. Framework](#library-vs-framework) - [What Component does better](#what-component-does-better) - [Swapping alternate implementations](#swapping-alternate-implementations) - [Uberjar / Packaging](#uberjar--packaging) @@ -51,26 +52,6 @@ Before moving on to differences, [here](https://news.ycombinator.com/item?id=246 ## So what are the differences? -### Objects vs. Namespaces - -One thing that feels a bit "unClojure" about Component is "Objects". Objects everywhere, and Objects for everything. -This is how Component "separates explicit dependencies" and "clears the bounaries". - -This is also how an Object Oriented language does it, which does not leave a lot of room for functions: -with Component most of the functions are _methods_ which is an important distinction. - -Mount relies on Clojure namespaces to clear the boundaries. No change from Clojure here: `defstate` in one namespace -can be easily `:require`d in another. - -### Start and Stop Order - -Component relies on a cool [dependency](https://github.com/stuartsierra/dependency) library to build -a graph of dependencies, and start/stop them via topological sort based on the dependencies in this graph. - -Since Mount relies on Clojure namespaces and `:require`/`:use`, the order of states -and their dependencies are revealed by the Clojure Compiler itself. Mount just records that order and replays -it back and forth on stop and start. - ### Component requires whole app buy in Component really only works if you build your entire app around its model: application is fully based on Components @@ -89,6 +70,15 @@ than to Again this is mostly a personal preference: the code works in both cases. +### Start and Stop Order + +Component relies on a cool [dependency](https://github.com/stuartsierra/dependency) library to build +a graph of dependencies, and start/stop them via topological sort based on the dependencies in this graph. + +Since Mount relies on Clojure namespaces and `:require`/`:use`, the order of states +and their dependencies are revealed by the Clojure Compiler itself. Mount just records that order and replays +it back and forth on stop and start. + ### Refactoring an existing application Since to get the most benefits of Component the approach is "all or nothing", to rewrite an existing application @@ -103,6 +93,17 @@ Component changes the way the code is structured. Depending on the size of the c Since Mount relies on Clojure namespaces (`:require`/`:use`), navigation across functions / states is exactly the same with or without Mount: there are no extra mental steps. +### Objects vs. Namespaces + +One thing that feels a bit "unClojure" about Component is "Objects". Objects everywhere, and Objects for everything. +This is how Component "separates explicit dependencies" and "clears the bounaries". + +This is also how an Object Oriented language does it, which does not leave a lot of room for functions: +with Component most of the functions are _methods_ which is an important distinction. + +Mount relies on Clojure namespaces to clear the boundaries. No change from Clojure here: `defstate` in one namespace +can be easily `:require`d in another. + ### Starting and stopping _parts_ of an application Component can't really start and stop parts of an application within the same "system". Other sub systems can be @@ -143,13 +144,21 @@ Mount is pretty much: no "ceremony". +### Library vs. Framework + +Mount uses namespaces and vars where Component uses records and protocols. + +Component manages protocols and records, and in order to do that it requires a whole app buyin, which makes it a _framework_. + +Mount does not need to manage namespaces and vars, since it is very well managed by the Clojure Compiler, which makes it a _library_. + ## What Component does better ### Swapping alternate implementations This is someting that is very useful for testing and is very easy to do in Component by simply assoc'ing onto a map. -Mount can do it to: https://github.com/tolitius/mount#swapping-alternate-implementations +Mount can do it too: https://github.com/tolitius/mount#swapping-alternate-implementations The reason it is in "Component does it better" section is because, while result is the same, merging maps is a bit simpler than: @@ -174,11 +183,11 @@ will be brought transitively. Here is more about mount [packaging](https://githu On the flip side, Component _system_ usually requires all the `:require`s, since in order to be built, it needs to "see" all the top level states. -###### _conclusion: it's simple in Mount as well, but requires an additional step._ +###### _conclusion: it's simple in Mount as well, but is left upto developer to require what's needed._ ### Multiple separate systems -With Component multiple separate systems can be started _in the same Clojure runtime_ with different settings. Which is very useful for testing. +With Component multiple separate systems can be started _in the same Clojure runtime_ with different settings. Which might be useful for testing. Mount keeps states in namespaces, hence the app becomes "[The One](https://en.wikipedia.org/wiki/Neo_(The_Matrix))", and there can't be "multiples The Ones". @@ -192,6 +201,8 @@ Testing is not alien to Mount and it knows how to do a thing or two: 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 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. + ###### _conclusion: needs more thinking._ ### Visualizing dependency graph @@ -201,3 +212,31 @@ Having this visualization is really helpful, especially during code discusions b Mount does not have this at the moment. It does have all the data to create such a visualization, perhaps even by building a graph out of the data it has just for this purpose. + +There is a [`(states-with-deps)`](https://github.com/tolitius/mount/blob/master/src/mount/tools/graph.cljc#L20) function that can help out: + +```clojure +dev=> (require '[mount.tools.graph :as graph]) + +dev=> (graph/states-with-deps) +({:name "#'app.conf/config", + :order 1, + :status #{:started}, + :deps #{}} + {:name "#'app.db/conn", + :order 2, + :status #{:started}, + :deps #{"#'app.conf/config"}} + {:name "#'app.www/nyse-app", + :order 3, + :status #{:started}, + :deps #{"#'app.conf/config"}} + {:name "#'app.example/nrepl", + :order 4, + :status #{:started}, + :deps #{"#'app.www/nyse-app" "#'app.conf/config"}}) +``` + +But it does not draw :), and it currently only supports Clojure, not ClojureScript. + +###### _conclusion: needs more thinking._