updating to unpounded lifecycle fns
This commit is contained in:
parent
18e4e229ce
commit
7624b9c7d5
6 changed files with 228 additions and 43 deletions
175
README.md
175
README.md
|
|
@ -7,6 +7,7 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
|
|||
module | branch | status
|
||||
----------|----------|----------
|
||||
mount | `master` | [](https://circleci.com/gh/tolitius/mount/tree/master)
|
||||
mount | `0.1.5` | [](https://circleci.com/gh/tolitius/mount/tree/0.1.5)
|
||||
|
||||
[](http://clojars.org/mount)
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
|
|||
- [Differences from Component](#differences-from-component)
|
||||
- [How](#how)
|
||||
- [Creating State](#creating-state)
|
||||
- [Value of Values](#value-of-values)
|
||||
- [Using State](#using-state)
|
||||
- [Dependencies](#dependencies)
|
||||
- [Talking States](#talking-states)
|
||||
|
|
@ -25,7 +27,14 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
|
|||
- [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)
|
||||
- [Suspending and Resuming](#suspending-and-resuming)
|
||||
- [Suspendable Lifecycle](#suspendable-lifecycle)
|
||||
- [Plugging into (reset)](#plugging-into-reset)
|
||||
- [Suspendable Example Application](#suspendable-example-application)
|
||||
- [Affected States](#affected-states)
|
||||
- [Logging](#logging)
|
||||
- [Mount and Develop!](#mount-and-develop)
|
||||
- [Running New York Stock Exchange](#running-new-york-stock-exchange)
|
||||
- [Web and Uberjar](#web-and-uberjar)
|
||||
|
|
@ -75,13 +84,13 @@ Creating state is easy:
|
|||
(defstate conn :start create-conn)
|
||||
```
|
||||
|
||||
where `create-conn` function is defined elsewhere, can be right above it.
|
||||
where the `create-conn` function is defined elsewhere, can be right above it.
|
||||
|
||||
In case this state needs to be cleaned / destryed between reloads, there is also `:stop`
|
||||
|
||||
```clojure
|
||||
(defstate conn :start create-conn
|
||||
:stop #(disconnect conn))
|
||||
:stop (disconnect conn))
|
||||
```
|
||||
|
||||
That is pretty much it. But wait, there is more.. this state is _a top level being_, which means it can be simply
|
||||
|
|
@ -94,6 +103,14 @@ dev=> conn
|
|||
#object[datomic.peer.LocalConnection 0x1661a4eb "datomic.peer.LocalConnection@1661a4eb"]
|
||||
```
|
||||
|
||||
#### Value of values
|
||||
|
||||
Lifecycle functions start/stop/suspend/resume can take both functions and values. This is "valuable" and also works:
|
||||
|
||||
```clojure
|
||||
(mount/defstate answer-to-the-ultimate-question-of-life-the-universe-and-everything :start 42)
|
||||
```
|
||||
|
||||
### Using State
|
||||
|
||||
For example let's say an `app` needs a connection above. No problem:
|
||||
|
|
@ -129,7 +146,7 @@ There are of course direct dependecies that `mount` respects:
|
|||
(:require [mount.core :refer [defstate]]))
|
||||
|
||||
(defstate app-config
|
||||
:start #(load-config "test/resources/config.edn"))
|
||||
:start (load-config "test/resources/config.edn"))
|
||||
```
|
||||
|
||||
this `app-config`, being top level, can be used in other namespaces, including the ones that create states:
|
||||
|
|
@ -139,7 +156,7 @@ this `app-config`, being top level, can be used in other namespaces, including t
|
|||
(:require [mount.core :refer [defstate]]
|
||||
[app.config :refer [app-config]]))
|
||||
|
||||
(defstate conn :start #(create-connection app-config))
|
||||
(defstate conn :start (create-connection app-config))
|
||||
```
|
||||
|
||||
[here](https://github.com/tolitius/mount/blob/master/test/app/nyse.clj)
|
||||
|
|
@ -248,10 +265,157 @@ One thing to note, whenever
|
|||
(mount/stop)
|
||||
```
|
||||
|
||||
is run after `start-with`, it rolls back to an original "state of states", i.e. `#'app.nyse/db` is `#'app.nyse/db` again. So a subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
|
||||
is run after `start-with`, it rolls back to an original "state of states", i.e. `#'app.nyse/db` is `#'app.nyse/db` again. So subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
|
||||
|
||||
Here is an [example](test/check/start_with_test.clj) test that starts an app with mocking Datomic connection and nREPL.
|
||||
|
||||
## 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`:
|
||||
|
||||
```clojure
|
||||
dev=> (mount/start)
|
||||
14:34:10.813 [nREPL-worker-0] INFO mount.core - >> starting.. app-config
|
||||
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.. app-config
|
||||
:stopped
|
||||
dev=>
|
||||
|
||||
dev=> (mount/start)
|
||||
14:34:58.673 [nREPL-worker-0] INFO mount.core - >> starting.. app-config
|
||||
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.
|
||||
|
||||
and some other use cases.
|
||||
|
||||
### Suspendable Lifecycle
|
||||
|
||||
In additiong 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.. app-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.. app-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'
|
||||
```
|
||||
|
||||
## 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/app-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.
|
||||
|
||||
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).
|
||||
|
||||
## Mount and Develop!
|
||||
|
||||
`mount` comes with an example [app](https://github.com/tolitius/mount/tree/master/test/app)
|
||||
|
|
@ -366,6 +530,7 @@ Switched to branch 'with-args'
|
|||
```
|
||||
|
||||
The documentation is [here](doc/runtime-arguments.md#passing-runtime-arguments).
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2015 tolitius
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
(ns mount.core
|
||||
(:require [clojure.tools.macro :as macro]))
|
||||
|
||||
;; (defonce ^:private session-id (System/currentTimeMillis))
|
||||
(defonce ^:private mount-state 42)
|
||||
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
|
||||
(defonce ^:private state-seq (atom 0))
|
||||
(defonce ^:private state-order (atom {}))
|
||||
(defonce ^:private running (atom {})) ;; to clean dirty states on redefs
|
||||
|
||||
;; supporting tools.namespace: (disable-reload!)
|
||||
(alter-meta! *ns* assoc ::load false) ;; to exclude the dependency
|
||||
|
|
@ -29,17 +29,39 @@
|
|||
(and suspend (not resume)) (throw
|
||||
(IllegalArgumentException. "suspendable state should have a resume function (i.e. missing :resume fn)"))))
|
||||
|
||||
(defn- with-ns [ns name]
|
||||
(str ns "/" name))
|
||||
|
||||
(defn- pounded? [f]
|
||||
(let [pound "(fn* [] "] ;;TODO: think of a better (i.e. typed) way to distinguish #(f params) from (fn [params] (...)))
|
||||
(.startsWith (str f) pound)))
|
||||
|
||||
(defn- unpound [f]
|
||||
(if (pounded? f)
|
||||
(nth f 2) ;; magic 2 is to get the body => ["fn*" "[]" "(fn body)"]
|
||||
f))
|
||||
|
||||
(defn- cleanup-if-dirty
|
||||
"in case a namespace is recompiled without calling (mount/stop),
|
||||
a running state instance will still be running.
|
||||
this function stops this 'lost' state instance.
|
||||
it is meant to be called by defstate before defining a new state"
|
||||
[state]
|
||||
(when-let [stop (@running state)]
|
||||
(stop)))
|
||||
|
||||
(defmacro defstate [state & body]
|
||||
(let [[state params] (macro/name-with-attributes state body)
|
||||
{:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)]
|
||||
(validate lifecycle)
|
||||
(cleanup-if-dirty (with-ns *ns* state))
|
||||
(let [s-meta (cond-> {:mount-state mount-state
|
||||
:order (make-state-seq state)
|
||||
:order (make-state-seq (with-ns *ns* state))
|
||||
:start `(fn [] ~start)
|
||||
:started? false}
|
||||
stop (assoc :stop `(fn [] (~stop)))
|
||||
suspend (assoc :suspend `(fn [] (~suspend)))
|
||||
resume (assoc :resume `(fn [] (~resume))))]
|
||||
:status #{:stopped}}
|
||||
stop (assoc :stop `(fn [] ~(unpound stop)))
|
||||
suspend (assoc :suspend `(fn [] ~suspend))
|
||||
resume (assoc :resume `(fn [] ~resume)))]
|
||||
`(defonce ~(with-meta state (merge (meta state) s-meta))
|
||||
(NotStartedState. ~(str state))))))
|
||||
|
||||
|
|
@ -48,44 +70,46 @@
|
|||
(swap! done conj (ns-resolve ns name))
|
||||
state))
|
||||
|
||||
(defn- up [var {:keys [ns name start started? resume suspended?] :as state} done]
|
||||
(when-not started?
|
||||
(let [s (try (if suspended?
|
||||
(defn- up [var {:keys [ns name start stop resume status] :as state} done]
|
||||
(when-not (:started status)
|
||||
(let [s (try (if (:suspended status)
|
||||
(record! state resume done)
|
||||
(record! state start done))
|
||||
(catch Throwable t
|
||||
(throw (RuntimeException. (str "could not start [" name "] due to") t))))]
|
||||
(intern ns (symbol name) s)
|
||||
(alter-meta! var assoc :started? true :suspended? false))))
|
||||
(swap! running assoc (with-ns ns name) stop)
|
||||
(alter-meta! var assoc :status #{:started}))))
|
||||
|
||||
(defn- down [var {:keys [ns name stop started? suspended?] :as state} done]
|
||||
(when (or started? suspended?)
|
||||
(defn- down [var {:keys [ns name stop status] :as state} done]
|
||||
(when (some status #{:started :suspended})
|
||||
(when stop
|
||||
(try
|
||||
(record! state stop done)
|
||||
(catch Throwable t
|
||||
(throw (RuntimeException. (str "could not stop [" name "] due to") t)))))
|
||||
(intern ns (symbol name) (NotStartedState. name)) ;; (!) if a state does not have :stop when _should_ this might leak
|
||||
(alter-meta! var assoc :started? false :suspended? false)))
|
||||
(swap! running dissoc (with-ns ns name))
|
||||
(alter-meta! var assoc :status #{:stopped})))
|
||||
|
||||
(defn- sigstop [var {:keys [ns name started? suspend resume] :as state} done]
|
||||
(when (and started? 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?)
|
||||
(defn- sigstop [var {:keys [ns name suspend resume status] :as state} 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 (try (record! state suspend done)
|
||||
(catch Throwable t
|
||||
(throw (RuntimeException. (str "could not suspend [" name "] due to") t))))]
|
||||
(intern ns (symbol name) s)))
|
||||
(alter-meta! var assoc :started? false :suspended? true)))
|
||||
(alter-meta! var assoc :status #{:suspended})))
|
||||
|
||||
(defn- sigcont [var {:keys [ns name start started? resume suspended?] :as state} done]
|
||||
(defn- sigcont [var {:keys [ns name start resume status] :as state} done]
|
||||
(when (instance? NotStartedState var)
|
||||
(throw (RuntimeException. (str "could not resume [" name "] since it is stoppped (i.e. not suspended)"))))
|
||||
(when suspended?
|
||||
(when (:suspended status)
|
||||
(let [s (try (record! state resume done)
|
||||
(catch Throwable t
|
||||
(throw (RuntimeException. (str "could not resume [" name "] due to") t))))]
|
||||
(intern ns (symbol name) s)
|
||||
(alter-meta! var assoc :started? true :suspended? false))))
|
||||
(alter-meta! var assoc :status #{:started}))))
|
||||
|
||||
;;TODO args might need more thinking
|
||||
(defn args [] @-args)
|
||||
|
|
@ -110,7 +134,7 @@
|
|||
(defn states-with-deps []
|
||||
(let [all (find-all-states)]
|
||||
(->> (map (comp #(add-deps % all)
|
||||
#(select-keys % [:name :order :ns :started? :suspended?])
|
||||
#(select-keys % [:name :order :ns :status])
|
||||
meta)
|
||||
all)
|
||||
(sort-by :order))))
|
||||
|
|
@ -129,9 +153,9 @@
|
|||
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 suspended?]}]
|
||||
([state origin {:keys [start stop suspend resume status]}]
|
||||
(assoc state :origin origin
|
||||
:suspended? suspended?
|
||||
:status status
|
||||
:start start :stop stop :suspend suspend :resume resume)))
|
||||
|
||||
(defn- rollback! [state]
|
||||
|
|
@ -140,7 +164,7 @@
|
|||
(alter-meta! state #(merge-lifecycles % origin)))))
|
||||
|
||||
(defn- substitute! [state with]
|
||||
(let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :suspended?])
|
||||
(let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :status])
|
||||
origin (meta state)
|
||||
sub (meta with)]
|
||||
(alter-meta! with assoc :sub? true)
|
||||
|
|
@ -148,8 +172,7 @@
|
|||
|
||||
(defn- unsub [state]
|
||||
(when (-> (meta state) :sub?)
|
||||
(alter-meta! state assoc :sub? nil
|
||||
:started false)))
|
||||
(alter-meta! state dissoc :sub?)))
|
||||
|
||||
(defn- all-without-subs []
|
||||
(remove (comp :sub? meta) (find-all-states)))
|
||||
|
|
@ -167,11 +190,8 @@
|
|||
|
||||
(defn stop-except [& states]
|
||||
(let [all (set (find-all-states))
|
||||
states (remove (set states) all)
|
||||
_ (dorun (map unsub states)) ;; unmark substitutions marked by "start-with"
|
||||
stopped (bring states down >)]
|
||||
(dorun (map rollback! states)) ;; restore to origin from "start-with"
|
||||
{:stopped stopped}))
|
||||
states (remove (set states) all)]
|
||||
(apply stop states)))
|
||||
|
||||
(defn start-with-args [xs & states]
|
||||
(reset! -args xs)
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
(start-server :bind host :port port))
|
||||
|
||||
;; nREPL is just another simple state
|
||||
(defstate nrepl :start #(start-nrepl (:nrepl app-config))
|
||||
:stop #(stop-server nrepl))
|
||||
(defstate nrepl :start (start-nrepl (:nrepl app-config))
|
||||
:stop (stop-server nrepl))
|
||||
|
||||
;; example of an app entry point
|
||||
(defn -main [& args]
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@
|
|||
edn/read-string))
|
||||
|
||||
(defstate app-config
|
||||
:start #(load-config "test/resources/config.edn"))
|
||||
:start (load-config "test/resources/config.edn"))
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
(.release conn) ;; usually it's not released, here just to illustrate the access to connection on (stop)
|
||||
(d/delete-database uri)))
|
||||
|
||||
(defstate conn :start #(new-connection app-config)
|
||||
:stop #(disconnect app-config conn))
|
||||
(defstate conn :start (new-connection app-config)
|
||||
:stop (disconnect app-config conn))
|
||||
|
||||
;; datomic schema (staging as an example)
|
||||
(defn create-schema [conn]
|
||||
|
|
|
|||
|
|
@ -27,5 +27,5 @@
|
|||
(run-jetty {:join? false
|
||||
:port (:port www)})))
|
||||
|
||||
(defstate nyse-app :start #(start-nyse app-config)
|
||||
:stop #(.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point
|
||||
(defstate nyse-app :start (start-nyse app-config)
|
||||
:stop (.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point
|
||||
|
|
|
|||
Loading…
Reference in a new issue