Merge branch 'master' into 0.1.9-SNAPSHOT
This commit is contained in:
commit
07eef10a77
3 changed files with 77 additions and 38 deletions
18
README.md
18
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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
|
|
|||
Loading…
Reference in a new issue