mount/README.md

756 lines
30 KiB
Markdown
Raw Normal View History

2015-11-13 04:18:07 +00:00
> I think that it's _extraordinarily important_ that we in computer science keep fun in computing
_**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](https://mitpress.mit.edu/sicp/full-text/book/book-Z-H-3.html)_
2015-10-20 01:33:56 +00:00
# mount
2015-11-14 21:37:39 +00:00
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)
2015-11-14 22:00:10 +00:00
[![Clojars Project](http://clojars.org/mount/latest-version.svg)](http://clojars.org/mount)
2015-10-20 02:36:39 +00:00
> <img src="doc/img/slack-icon.png" width="30px"> _any_ questions or feedback: [`#mount`](https://clojurians.slack.com/messages/mount/) clojurians slack channel (or just [open an issue](https://github.com/tolitius/mount/issues))
-
2015-10-20 19:50:07 +00:00
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Why?](#why)
2015-11-14 20:22:04 +00:00
- [Differences from Component](#differences-from-component)
2015-10-20 19:50:07 +00:00
- [How](#how)
- [Creating State](#creating-state)
- [Using State](#using-state)
- [Dependencies](#dependencies)
- [Talking States](#talking-states)
2015-12-01 03:36:11 +00:00
- [Value of Values](#value-of-values)
2015-10-20 19:50:07 +00:00
- [The Importance of Being Reloadable](#the-importance-of-being-reloadable)
2015-10-24 15:05:45 +00:00
- [Start and Stop Order](#start-and-stop-order)
2015-11-14 16:15:12 +00:00
- [Start and Stop Parts of Application](#start-and-stop-parts-of-application)
2015-11-15 19:54:23 +00:00
- [Start an Application Without Certain States](#start-an-application-without-certain-states)
2015-11-21 20:11:46 +00:00
- [Stop an Application Except Certain States](#stop-an-application-except-certain-states)
2015-11-16 03:45:12 +00:00
- [Swapping Alternate Implementations](#swapping-alternate-implementations)
2016-01-24 03:02:40 +00:00
- [Swapping States with Values](#swapping-states-with-values)
- [Swapping States with States](#swapping-states-with-states)
2015-11-21 20:11:46 +00:00
- [Suspending and Resuming](#suspending-and-resuming)
- [Suspendable Lifecycle](#suspendable-lifecycle)
- [Plugging into (reset)](#plugging-into-reset)
- [Suspendable Example Application](#suspendable-example-application)
2015-12-10 15:57:00 +00:00
- [ClojureScript is Clojure](doc/clojurescript.md#managing-state-in-clojurescript)
2015-12-28 17:01:11 +00:00
- [Packaging](#packaging)
2015-12-22 01:42:29 +00:00
- [Affected States](#affected-states)
- [Recompiling Namespaces with Running States](#recompiling-namespaces-with-running-states)
2016-01-17 07:18:39 +00:00
- [Cleaning up Deleted States](#cleaning-up-deleted-states)
- [Logging](#logging)
2015-12-27 20:10:22 +00:00
- [Clojure Version](#clojure-version)
2015-10-20 19:50:07 +00:00
- [Mount and Develop!](#mount-and-develop)
- [Running New York Stock Exchange](#running-new-york-stock-exchange)
- [New York Stock Exchange Maintenance](#new-york-stock-exchange-maintenance)
2015-11-13 22:16:52 +00:00
- [Web and Uberjar](#web-and-uberjar)
- [Runtime Arguments](#runtime-arguments)
2015-10-20 19:50:07 +00:00
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
2015-10-20 02:00:10 +00:00
## Why?
2015-10-20 01:33:56 +00:00
2015-11-14 20:22:04 +00:00
Clojure is
2015-10-20 01:33:56 +00:00
2015-11-14 20:22:04 +00:00
* powerful
2015-10-20 01:33:56 +00:00
* simple
* and _fun_
2015-11-14 20:22:04 +00:00
Depending on how application state is managed during development, the above three superpowers can either stay,
2015-10-20 02:00:10 +00:00
go somewhat, or go completely.
2015-10-20 01:33:56 +00:00
If Clojure REPL (i.e. `lein repl`, `boot repl`) fired up instantly, the need to reload application state
inside the REPL would go away. But at the moment, and for some time in the future, managing state by making it
reloadable within the same REPL session is important to retain all the Clojure superpowers.
Here is a good [breakdown](http://blog.ndk.io/clojure-bootstrapping.html) on the Clojure REPL
startup time, and it is [not because of JVM](http://blog.ndk.io/jvm-slow-startup.html).
2015-10-20 01:33:56 +00:00
`mount` is here to preserve all the Clojure superpowers while making _the application state_ enjoyably reloadable.
2015-10-20 01:33:56 +00:00
There is another Clojure superpower that `mount` is made to retain: Clojure community.
Pull request away, let's solve this thing!
### Differences from Component
`mount` is an alternative to the [component](https://github.com/stuartsierra/component) approach with notable [differences](doc/differences-from-component.md#differences-from-component).
2015-10-20 02:00:10 +00:00
## How
2015-10-20 01:33:56 +00:00
```clojure
2015-11-17 05:09:45 +00:00
(require '[mount.core :refer [defstate]])
2015-10-20 01:33:56 +00:00
```
### Creating State
Creating state is easy:
```clojure
(defstate conn :start create-conn)
2015-10-20 01:33:56 +00:00
```
where the `create-conn` function is defined elsewhere, can be right above it.
2015-10-20 01:33:56 +00:00
2015-12-16 16:24:48 +00:00
In case this state needs to be cleaned / destroyed between reloads, there is also `:stop`
2015-10-20 01:33:56 +00:00
```clojure
(defstate conn :start create-conn
2015-11-30 18:12:44 +00:00
:stop (disconnect conn))
2015-10-20 01:33:56 +00:00
```
2015-11-14 20:22:04 +00:00
That is pretty much it. But wait, there is more.. this state is _a top level being_, which means it can be simply
2015-10-28 20:36:52 +00:00
`required` by other namespaces or in REPL:
```clojure
dev=> (require '[app.nyse :refer [conn]])
nil
dev=> conn
#object[datomic.peer.LocalConnection 0x1661a4eb "datomic.peer.LocalConnection@1661a4eb"]
```
2015-10-20 01:33:56 +00:00
### Using State
For example let's say an `app` needs a connection above. No problem:
```clojure
(ns app
(:require [above :refer [conn]]))
```
2015-10-20 02:00:10 +00:00
where `above` is an arbitrary namespace that defines the above state / connection.
2015-10-20 01:33:56 +00:00
## Dependencies
If the whole app is one big application context (or `system`), cross dependencies with a solid dependency graph
is an integral part of the system.
But if a state is a simple top level being, these beings can coexist with each other and with other
namespaces by being `required` instead.
2015-11-14 20:22:04 +00:00
If a managing state library requires a whole app buy-in, where everything is a bean or a component,
it is a framework, and dependency graph is usually quite large and complex,
since it has _everything_ (every piece of the application) in it.
2015-10-20 01:33:56 +00:00
But if stateful things are kept lean and low level (i.e. I/O, queues, threads, connections, etc.), dependency graphs are simple and small, and everything else is just namespaces and functions: the way it should be.
2015-10-20 01:33:56 +00:00
### Talking States
2016-01-09 16:37:56 +00:00
There are of course direct dependencies that `mount` respects:
2015-10-20 01:33:56 +00:00
```clojure
(ns app.config
2015-11-17 05:09:45 +00:00
(:require [mount.core :refer [defstate]]))
2015-10-20 01:33:56 +00:00
(defstate config
2015-11-30 18:12:44 +00:00
:start (load-config "test/resources/config.edn"))
2015-10-20 01:33:56 +00:00
```
this `config`, being top level, can be used in other namespaces, including the ones that create states:
2015-10-20 01:33:56 +00:00
```clojure
(ns app.database
2015-11-17 05:09:45 +00:00
(:require [mount.core :refer [defstate]]
[app.config :refer [config]]))
2015-10-20 01:33:56 +00:00
(defstate conn :start (create-connection config))
2015-10-20 01:33:56 +00:00
```
[here](dev/clj/app/www.clj#L30)
is an example of a web server that "depends" on a similar `config`.
2015-10-20 01:33:56 +00:00
2015-12-01 03:36:11 +00:00
## Value of values
Lifecycle functions start/stop/suspend/resume 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)
2015-12-01 03:36:11 +00:00
```
2016-01-24 03:02:40 +00:00
While it would be useful in REPL and for testing, real application states would usually have start / stop logic, in other words, the real lifecycle.
Besides scalar values, lifecycle functions can take anonymous functions, partial functions, function references, etc.. Here are some examples:
2015-12-01 03:36:11 +00:00
```clojure
(defn f [n]
(fn [m]
(+ n m)))
(defn g [a b]
(+ a b))
(defn- pf [n]
(+ 41 n))
(defn fna []
42)
2015-12-01 03:36:11 +00:00
(defstate scalar :start 42)
(defstate fun :start #(inc 41))
(defstate with-fun :start (inc 41))
(defstate with-partial :start (partial g 41))
(defstate f-in-f :start (f 41))
(defstate f-no-args-value :start (fna))
(defstate f-no-args :start fna)
2015-12-01 03:36:11 +00:00
(defstate f-args :start g)
(defstate f-value :start (g 41 1))
(defstate private-f :start pf)
```
2015-12-23 14:18:07 +00:00
Check out [fun-with-values-test](test/core/mount/test/fun_with_values.cljc) for more details.
2015-12-01 03:36:11 +00:00
2015-10-24 15:04:13 +00:00
## The Importance of Being Reloadable
`mount` has start and stop functions that will walk all the states created with `defstate` and start / stop them
2016-01-09 16:37:56 +00:00
accordingly: i.e. will call their `:start` and `:stop` defined functions. Hence the whole application state can be reloaded in REPL e.g.:
2015-11-07 17:51:10 +00:00
```
2015-11-17 05:09:45 +00:00
dev=> (require '[mount.core :as mount])
2015-11-07 17:51:10 +00:00
dev=> (mount/stop)
dev=> (mount/start)
```
2015-10-24 15:04:13 +00:00
2016-01-08 20:54:25 +00:00
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.
2015-12-23 16:18:05 +00:00
Here is a [dev.clj](dev/clj/dev.clj) as an example, that sums up to:
2015-10-24 15:04:13 +00:00
```clojure
(defn go []
(start)
:ready)
(defn reset []
(stop)
(tn/refresh :after 'dev/go))
```
2015-11-14 20:22:04 +00:00
the `(reset)` is then used in REPL to restart / reload application state without the need to restart the REPL itself.
2015-10-24 15:04:13 +00:00
2015-10-20 19:41:53 +00:00
## Start and Stop Order
2015-11-14 20:22:04 +00:00
Since dependencies are "injected" by `require`ing on the namespace level, `mount` **trusts the Clojure compiler** to
2015-11-07 17:53:29 +00:00
maintain the start and stop order for all the `defstates`.
2015-10-20 19:41:53 +00:00
The "start" order is then recorded and replayed on each `(reset)`.
The "stop" order is simply `(reverse "start order")`:
```clojure
dev=> (reset)
2015-10-21 12:27:09 +00:00
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
2015-10-21 12:27:09 +00:00
:reloading (app.config app.nyse app.utils.datomic app)
08:21:39.462 [nREPL-worker-1] DEBUG mount - >> starting.. config
2015-10-21 12:27:09 +00:00
08:21:39.463 [nREPL-worker-1] DEBUG mount - >> starting.. conn
08:21:39.481 [nREPL-worker-1] DEBUG mount - >> starting.. nrepl
:ready
```
2015-10-20 19:41:53 +00:00
2015-12-10 04:04:17 +00:00
You can see examples of start and stop flows in the [example app](README.md#mount-and-develop).
2015-10-20 19:41:53 +00:00
2015-11-14 16:15:12 +00:00
## 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".
`mount`'s lifecycle functions, i.e. start/stop/suspend/resume, can _optionally_ take states as vars (i.e. prefixed with their namespaces):
2015-11-14 16:15:12 +00:00
```clojure
(mount/start #'app.config/config #'app.nyse/conn)
2015-11-14 16:15:12 +00:00
...
(mount/stop #'app.config/config #'app.nyse/conn)
2015-11-14 16:15:12 +00:00
```
which will _only_ start/stop `config` and `conn` (won't start/stop any other states).
2015-11-14 16:15:12 +00:00
Here is an [example](test/core/mount/test/parts.cljc) test that uses only two namespaces checking that the third one is not started.
2015-11-14 16:15:12 +00:00
2015-11-15 19:54:23 +00:00
## Start an Application Without Certain States
Whether it is in REPL or during testing, it is often useful to start an application _without_ certain states. These can be queue listeners that are not needed at REPL time, or a subset of an application to test.
The `start-without` function can do just that:
```clojure
(mount/start-without #'app.feeds/feed-listener
2015-11-15 19:54:23 +00:00
#'app/nrepl)
```
which will start an application without starting `feed-listener` and `nrepl` states.
Here is an [example](test/core/mount/test/start_without.cljc) test that excludes Datomic connection and nREPL from an application on start.
2015-11-16 03:45:12 +00:00
## Swapping Alternate Implementations
2016-01-09 16:37:56 +00:00
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.
2015-11-16 03:45:12 +00:00
2016-01-24 03:02:40 +00:00
### Swapping States with Values
The `start-with` function takes values as substitues.
Say we have a `send-sms` state:
```clojure
(ns app.sms)
;; ...
(defstate send-sms :start (create-sms-sender
(:sms config)))
```
When running tests it would be great _not_ to send the real text messages, but rather send them all to a local core.async channel instead:
2015-11-16 03:45:12 +00:00
```clojure
2016-01-24 03:02:40 +00:00
(let [sms-ch (chan)
send-sms (fn [sms] (go (>! sms-ch sms)))]
(mount/start-with {#'app.sms/send-sms send-sms}) ;; <<<< swapping the "send-sms" state with a test function
;; testing.. checking "sms-ch" channel
(mount/stop))
2015-11-16 03:45:12 +00:00
```
2016-01-24 03:02:40 +00:00
`start-with` takes a map of states with their substitutes. For example `#'app.sms/send-sms` here is the real deal SMS sender that is being substituted with a `send-sms` test function.
### Swapping States with States
The `start-with-states` function takes other states as substitues:
```clojure
(mount/start-with {#'app.neo/db #'app.test/test-db
#'app.neo/publisher #'app.test/test-publisher})
```
`start-with-states` takes a map of states with their substitutes. For example `#'app.nyse/db` here is the real deal (remote) DB that is being substituted with `#'app.test/test-db` state, which could be anything, a map, an in memory DB, etc.
--
2015-11-16 03:45:12 +00:00
2016-01-24 03:02:40 +00:00
One thing to note is whenever
2015-11-16 03:45:12 +00:00
```clojure
(mount/stop)
```
2016-01-24 03:02:40 +00:00
is run after `start-with`/`start-with-states`, it rolls back to an original "state of states", i.e. `#'app.neo/db` is `#'app.neo/db` again. So subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
2015-11-16 03:45:12 +00:00
2016-01-24 03:02:40 +00:00
Here is an [example](test/core/mount/test/start_with_states.cljc) test that starts an app with mocking Datomic connection and nREPL.
2015-11-15 19:54:23 +00:00
2015-11-21 20:11:46 +00:00
## Stop an Application Except Certain States
Calling `(mount/stop)` will stop all the application states. In case everything needs to be stopped _besides certain ones_, it can be done with `(mount/stop-except)`.
Here is an example of restarting the application without bringing down `#'app.www/nyse-app`:
2015-11-21 20:11:46 +00:00
```clojure
dev=> (mount/start)
14:34:10.813 [nREPL-worker-0] INFO mount.core - >> starting.. config
2015-11-21 20:11:46 +00:00
14:34:10.814 [nREPL-worker-0] INFO mount.core - >> starting.. conn
14:34:10.814 [nREPL-worker-0] INFO app.db - creating a connection to datomic: datomic:mem://mount
14:34:10.838 [nREPL-worker-0] INFO mount.core - >> starting.. nyse-app
14:34:10.843 [nREPL-worker-0] DEBUG o.e.j.u.component.AbstractLifeCycle - STARTED SelectChannelConnector@0.0.0.0:4242
14:34:10.843 [nREPL-worker-0] DEBUG o.e.j.u.component.AbstractLifeCycle - STARTED org.eclipse.jetty.server.Server@194f37af
14:34:10.844 [nREPL-worker-0] INFO mount.core - >> starting.. nrepl
:started
dev=> (mount/stop-except #'app.www/nyse-app)
14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. nrepl
14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. conn
14:34:47.766 [nREPL-worker-0] INFO app.db - disconnecting from datomic:mem://mount
14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. config
2015-11-21 20:11:46 +00:00
:stopped
dev=>
dev=> (mount/start)
14:34:58.673 [nREPL-worker-0] INFO mount.core - >> starting.. config
2015-11-21 20:11:46 +00:00
14:34:58.674 [nREPL-worker-0] INFO app.config - loading config from test/resources/config.edn
14:34:58.674 [nREPL-worker-0] INFO mount.core - >> starting.. conn
14:34:58.674 [nREPL-worker-0] INFO app.db - creating a connection to datomic: datomic:mem://mount
14:34:58.693 [nREPL-worker-0] INFO mount.core - >> starting.. nrepl
:started
```
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.
2015-11-21 20:11:46 +00:00
and some other use cases.
### Suspendable Lifecycle
2016-01-08 11:13:28 +00:00
In addition to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones:
2015-11-21 20:11:46 +00:00
```clojure
2015-11-27 18:03:58 +00:00
(defstate web-server :start start-server
:resume resume-server
:stop stop-server)
2015-11-21 20:11:46 +00:00
```
`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
2015-11-21 20:11:46 +00:00
: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
2015-11-21 20:11:46 +00:00
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'
```
2015-12-22 01:42:29 +00:00
## Recompiling Namespaces with Running States
Mount will detect when a namespace with states (i.e. with `(defstate ...)`) was reloaded/recompiled,
and will check every state in this namespace whether it was running at the point of recompilation. If it was, _it will restart it_:
2015-12-22 01:42:29 +00:00
* if a state has a `:stop` function, mount will invoke it on the old version of state (i.e. cleanup)
* it will call a "new" `:start` function _after_ this state is recompiled/redefined
2015-12-22 01:42:29 +00:00
Mount won't keep it a secret, it'll tell you about all the states that had to be restarted during namespace reload/recompilation:
2015-12-22 01:42:29 +00:00
<img src="doc/img/ns-recompile.png" width="500px">
2015-12-22 01:42:29 +00:00
same is true for recompiling and reloading (figwheel, boot-reload, etc.) namespaces in ClojureScript:
<img src="doc/img/cljs-ns-reload.png" width="500px">
Providing a `:stop` function _is_ optional, but in case a state needs to be cleaned between restarts or on a system shutdown,
`:stop` is highly recommended.
2016-01-17 07:18:39 +00:00
## Cleaning up Deleted States
2016-01-17 07:40:24 +00:00
Mount will detect when a state was renamed/deleted from a namespace, and will do two things:
2016-01-17 07:18:39 +00:00
* if a state had a `:stop` function, mount will invoke it on the old version of state (i.e. cleanup)
* will remove any knowledge of this state internally
Here is an example:
```clojure
dev=> (defstate won't-be-here-long :start (println "I am starting... ")
:stop (println "I am stopping... "))
#'dev/won't-be-here-long
dev=>
dev=> (mount/start #'dev/won't-be-here-long)
INFO app.utils.logging - >> starting.. #'dev/won't-be-here-long
I am starting...
{:started ["#'dev/won't-be-here-long"]}
dev=>
```
"deleting" it from REPL, and starting all the states:
```clojure
dev=> (ns-unmap 'dev 'won't-be-here-long)
nil
dev=> (mount/start)
"<< stopping.. #'dev/won't-be-here-long (it was deleted)"
I am stopping...
INFO app.utils.logging - >> starting.. #'app.conf/config
INFO app.utils.logging - >> starting.. #'app.db/conn
INFO app.utils.logging - >> starting.. #'app.www/nyse-app
INFO app.utils.logging - >> starting.. #'app.example/nrepl
{:started ["#'app.conf/config" "#'app.db/conn" "#'app.www/nyse-app" "#'app.example/nrepl"]}
```
Mount detected that `#'dev/won't-be-here-long` was deleted, hence:
```clojure
<< stopping.. #'dev/won't-be-here-long (it was deleted)
```
2015-12-28 17:01:11 +00:00
## Packaging
2015-12-28 17:05:54 +00:00
Since `mount` relies on the Clojure/Script Compiler to learn about all the application states, before `mount/start` is called all the namespaces that have `defstate`s need to be compiled.
2015-12-28 17:01:11 +00:00
At the development time this requirement is mostly transparent, since these namespaces are compiled with nREPL, or refreshed with "tools.namespace", etc. But it becomes important when _packaging_ an application or when starting a web application via [lein-ring](https://github.com/weavejester/lein-ring#general-options)'s or [boot-http](https://github.com/pandeiro/boot-http#-i----init-and--c----cleanup)'s `:init` hooks.
Depending on a structure and a kind of an application, this means that these namespaces need to be _`:required`_ prior to a call to `mount/start` when packaging the app as a stand alone JAR or a WAR.
This can be easily done with choosing an application entry point, which could be a web handler namespace with routes or just an arbitrary app namespace (i.e. `my.app`). In this app entry point namespace all other namespaces that have `defstate` would be `:require`d and a call to the `mount/start` function would be defined:
```clojure
(ns my.app
(:require [a]
[b]
[c]
[mount.core :as mount]))
2015-12-28 17:01:11 +00:00
(defn rock-n-roll [] ;; or (defn -main [args].. )
(mount/start))
```
this would ensure that at the time `(rock-n-roll)` is called, all the namespaces with states were compiled (i.e. mount knows about them). `(rock-n-roll)` can be used in/as a -main function or as a web hook such as `:init`.
In practice only a few namespaces need to be `:require`d, since others will be brought in transitively (i.e. by already required namespaces). From the `my.app` example above, say we had namespaces `d`, `e` and `f` that are required by `a`, and `g` and `h` that are required by `b`. They (`d`, `e`, `f`, `g` and `h`) _won't_ need to be required by `my.app`, since `a` and `b` would "bring" them in.
## Affected States
Every time a lifecycle function (start/stop/suspend/resume) 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]}
```
```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]}
```
An interesting bit here is a vector vs. a set: all the states are returned _in the order they were changed_.
## Logging
> All the mount examples have `>> starting..` / `<< stopping..` logging messages, but when I develop an application with mount I don't see them.
Valid question. It was a [conscious choice](https://github.com/tolitius/mount/issues/15) not to depend on any particular logging library, since there are few to select from, and this decision is best left to the developer who may choose to use mount.
Since mount is a _library_ it should _not_ bring any dependencies unless its functionality directly depends on them.
> But I still these logging statements in the examples.
2015-11-23 18:35:16 +00:00
The way this is done is via an excellent [robert hooke](https://github.com/technomancy/robert-hooke/). Example applications live in `test`, so does the [utility](https://github.com/tolitius/mount/blob/75d7cdc610ce38623d4d3aea1da3170d1c9a3b4b/test/app/utils/logging.clj#L44) that adds logging to all the mount's lifecycle functions on start in [dev.clj](https://github.com/tolitius/mount/blob/75d7cdc610ce38623d4d3aea1da3170d1c9a3b4b/dev/dev.clj#L21).
2015-12-27 20:10:22 +00:00
## Clojure Version
2016-01-09 16:37:56 +00:00
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.
2015-12-27 20:10:22 +00:00
2015-10-20 02:00:10 +00:00
## Mount and Develop!
2015-10-20 01:33:56 +00:00
2015-12-10 04:01:29 +00:00
`mount` comes with an example [app](dev/clj/app)
that has 4 states:
2015-10-20 01:33:56 +00:00
2015-10-20 02:00:10 +00:00
* `config`, loaded from the files and refreshed on each `(reset)`
2015-11-14 20:00:47 +00:00
* `datomic connection` that uses the config to create itself
* `nyse web app` which is a web server with compojure routes (i.e. the actual app)
* `nrepl` that uses config to bind to host/port
2015-10-20 01:33:56 +00:00
### Running New York Stock Exchange
2015-12-22 02:50:30 +00:00
To try it out, clone `mount`, get to REPL (`boot repl` or `lein repl`) and switch to `(dev)`:
2015-10-20 01:33:56 +00:00
```clojure
2015-12-22 02:50:30 +00:00
$ boot repl
2015-10-20 01:33:56 +00:00
user=> (dev)
#object[clojure.lang.Namespace 0xcf1a0cc "dev"]
```
start/restart/reset everything using `(reset)`:
```clojure
dev=> (reset)
2015-12-22 02:50:30 +00:00
:reloading (mount.tools.macro mount.core app.utils.logging app.conf app.db app.utils.datomic app.nyse app.www app.example dev)
INFO app.utils.logging - >> starting.. #'app.conf/config
INFO app.conf - loading config from dev/resources/config.edn
INFO app.utils.logging - >> starting.. #'app.db/conn
INFO app.db - conf: {:datomic {:uri datomic:mem://mount}, :www {:port 4242}, :h2 {:classname org.h2.Driver, :subprotocol h2, :subname jdbc:h2:mem:mount, :user sa, :password }, :rabbit {:api-port 15672, :password guest, :queue r-queue, :username guest, :port 5672, :node jabit, :exchange-type direct, :host 192.168.1.1, :vhost /captoman, :auto-delete-q? true, :routing-key , :exchange foo}, :nrepl {:host 0.0.0.0, :port 7878}}
INFO app.db - creating a connection to datomic: datomic:mem://mount
INFO app.utils.logging - >> starting.. #'app.www/nyse-app
INFO app.utils.logging - >> starting.. #'app.example/nrepl
2015-10-20 01:33:56 +00:00
dev=>
```
everything is started and can be played with:
```clojure
dev=> (add-order conn {:ticker "GOOG" :bid 665.51M :offer 665.59M :qty 100})
dev=> (add-order conn {:ticker "GOOG" :bid 665.50M :offer 665.58M :qty 300})
2015-10-20 01:33:56 +00:00
dev=> (find-orders conn "GOOG")
2015-10-20 01:33:56 +00:00
({:db/id 17592186045418, :order/symbol "GOOG", :order/bid 665.51M, :order/qty 100, :order/offer 665.59M}
{:db/id 17592186045420, :order/symbol "GOOG", :order/bid 665.50M, :order/qty 300, :order/offer 665.58M})
```
2015-12-22 02:50:30 +00:00
since there is also a web server running, we can add orders with HTTP POST (from a different terminal window):
```clojure
$ curl -X POST -d "ticker=TSLA&qty=100&bid=232.38&offer=232.43" "http://localhost:4242/nyse/orders"
{"added":{"ticker":"TSLA","qty":"100","bid":"232.38","offer":"232.43"}}
```
```clojure
dev=> (find-orders conn "TSLA")
2015-12-22 02:50:30 +00:00
({:db/id 17592186045422, :order/symbol "TSLA", :order/bid 232.38M, :order/qty 100, :order/offer 232.43M})
```
once something is changed in the code, or you just need to reload everything, do `(reset)`.
_note: a simple `(mount/stop)` / `(mount/start)` will also work, `(reset)` is for "convenience + ns refresh":_
2015-10-20 01:33:56 +00:00
```clojure
dev=> (reset)
2015-12-22 02:50:30 +00:00
INFO app.utils.logging - << stopping.. #'app.example/nrepl
INFO app.utils.logging - << stopping.. #'app.www/nyse-app
INFO app.utils.logging - << stopping.. #'app.db/conn
INFO app.db - disconnecting from datomic:mem://mount
INFO app.utils.logging - << stopping.. #'app.conf/config
:reloading (app.conf app.db app.nyse app.www app.example dev)
INFO app.utils.logging - >> starting.. #'app.conf/config
INFO app.conf - loading config from dev/resources/config.edn
INFO app.utils.logging - >> starting.. #'app.db/conn
INFO app.db - conf: {:datomic {:uri datomic:mem://mount}, :www {:port 4242}, :h2 {:classname org.h2.Driver, :subprotocol h2, :subname jdbc:h2:mem:mount, :user sa, :password }, :rabbit {:api-port 15672, :password guest, :queue r-queue, :username guest, :port 5672, :node jabit, :exchange-type direct, :host 192.168.1.1, :vhost /captoman, :auto-delete-q? true, :routing-key , :exchange foo}, :nrepl {:host 0.0.0.0, :port 7878}}
INFO app.db - creating a connection to datomic: datomic:mem://mount
INFO app.utils.logging - >> starting.. #'app.www/nyse-app
INFO app.utils.logging - >> starting.. #'app.example/nrepl
2015-10-20 01:33:56 +00:00
:ready
```
notice that it stopped and started again.
In `app.db` connection `:stop` calls a `disconnect` function where a [database is deleted](https://github.com/tolitius/mount/blob/e3066fe024f89420bd4463a433c5d3b893b7b315/dev/clj/app/db.clj#L18). Hence after `(reset)` was called the app was brought its starting point: [database was created](https://github.com/tolitius/mount/blob/e3066fe024f89420bd4463a433c5d3b893b7b315/dev/clj/app/db.clj#L11) by the
`:start` that calls a `new-connection` function, and db schema is [created](https://github.com/tolitius/mount/blob/e3066fe024f89420bd4463a433c5d3b893b7b315/dev/clj/app/www.clj#L26) by `nyse.app`.
2015-12-22 02:50:30 +00:00
But again no orders:
2015-10-20 01:33:56 +00:00
```clojure
dev=> (find-orders conn "GOOG")
2015-12-22 02:50:30 +00:00
()
dev=> (find-orders conn "TSLA")
2015-12-22 02:50:30 +00:00
()
2015-10-20 01:33:56 +00:00
```
hence the app is in its "clean" state, and ready to rock and roll as right after the REPL started:
```clojure
dev=> (add-order conn {:ticker "TSLA" :bid 232.381M :offer 232.436M :qty 250})
2015-10-20 01:33:56 +00:00
dev=> (find-orders conn "TSLA")
({:db/id 17592186045418, :order/symbol "TSLA", :order/bid 232.381M, :order/qty 250, :order/offer 232.436M})
2015-12-22 02:50:30 +00:00
```
2015-10-20 01:33:56 +00:00
2015-12-22 02:50:30 +00:00
### New York Stock Exchange Maintenance
2016-01-09 16:37:56 +00:00
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:
2015-12-22 02:50:30 +00:00
```clojure
dev=> (mount/stop #'app.www/nyse-app)
INFO app.utils.logging - << stopping.. #'app.www/nyse-app
{:stopped ["#'app.www/nyse-app"]}
dev=>
```
```bash
$ curl localhost:4242
curl: (7) Failed to connect to localhost port 4242: Connection refused
```
everything but the web server works as before:
```clojure
dev=> (find-orders conn "TSLA")
2015-12-22 02:50:30 +00:00
({:db/id 17592186045420, :order/symbol "TSLA", :order/bid 232.381M, :order/qty 250, :order/offer 232.436M})
dev=>
```
once we found who `DDoS`ed us on `:4242`, and punished them, we can restart the web server:
2015-12-22 02:50:30 +00:00
```clojure
dev=> (mount/start #'app.www/nyse-app)
INFO app.utils.logging - >> starting.. #'app.www/nyse-app
{:started ["#'app.www/nyse-app"]}
dev=>
```
```clojure
$ curl localhost:4242
welcome to the mount sample app!
2015-10-20 01:33:56 +00:00
```
2015-11-13 22:16:52 +00:00
## Web and Uberjar
2015-11-14 02:48:17 +00:00
There is an `uberjar` branch with an example webapp and it's uberjar sibling. Before trying it:
2015-11-13 22:16:52 +00:00
```clojure
$ git checkout uberjar
Switched to branch 'uberjar'
```
2015-11-14 02:48:17 +00:00
The documentation is [here](doc/uberjar.md#creating-reloadable-uberjarable-app).
2015-11-13 22:16:52 +00:00
2015-11-14 02:48:17 +00:00
## Runtime Arguments
There is an `with-args` branch with an example app that takes command line params
```clojure
$ git checkout with-args
Switched to branch 'with-args'
```
The documentation is [here](doc/runtime-arguments.md#passing-runtime-arguments).
2015-10-20 12:36:00 +00:00
## License
2015-10-20 01:33:56 +00:00
Copyright © 2015 tolitius
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.