[#5]: suspendable example app.. [in progress]
This commit is contained in:
parent
4e97280be9
commit
9d7c5576f8
13 changed files with 128 additions and 315 deletions
14
dev/dev.clj
14
dev/dev.clj
|
|
@ -13,21 +13,15 @@
|
|||
[clojure.test :as test]
|
||||
;; [clojure.core.async :refer [>!! <!! >! <! go-loop alt! timeout]]
|
||||
[clojure.tools.namespace.repl :as tn]
|
||||
[check.parts-test]
|
||||
[check.start-with-test]
|
||||
[check.suspend-resume-test]
|
||||
[mount.core :as mount]
|
||||
[app :refer [create-nyse-schema find-orders add-order]])) ;; <<<< replace this your "app" namespace(s) you want to be available at REPL time
|
||||
[app.www]
|
||||
[app.nyse :refer [create-nyse-schema find-orders add-order]])) ;; <<<< replace this your "app" namespace(s) you want to be available at REPL time
|
||||
|
||||
(defn start []
|
||||
(mount/start-without #'check.start-with-test/test-conn
|
||||
#'check.start-with-test/test-nrepl
|
||||
#'check.parts-test/should-not-start
|
||||
#'check.suspend-resume-test/web-server
|
||||
#'check.suspend-resume-test/q-listener)) ;; example on how to start app without certain states
|
||||
(mount/start))
|
||||
|
||||
(defn stop []
|
||||
(mount/stop))
|
||||
(mount/stop-except #'app.www/nyse-app))
|
||||
|
||||
(defn refresh []
|
||||
(stop)
|
||||
|
|
|
|||
15
project.clj
15
project.clj
|
|
@ -14,5 +14,18 @@
|
|||
|
||||
:profiles {:dev {:source-paths ["dev" "test/app"]
|
||||
:dependencies [[yesql "0.5.1"]
|
||||
[compojure "1.4.0"]
|
||||
[ring/ring-jetty-adapter "1.1.0"]
|
||||
[cheshire "5.5.0"]
|
||||
[org.clojure/tools.nrepl "0.2.11"]
|
||||
[com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]}})
|
||||
[com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]}
|
||||
|
||||
;; "test" is in sources here to just "demo" the uberjar without poluting mount "src"
|
||||
:uberjar {:source-paths ["test/app"]
|
||||
:dependencies [[compojure "1.4.0"]
|
||||
[ring/ring-jetty-adapter "1.1.0"]
|
||||
[cheshire "5.5.0"]
|
||||
[org.clojure/tools.nrepl "0.2.11"]
|
||||
[com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]
|
||||
:main app
|
||||
:aot :all}})
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
(ns app
|
||||
(:require [datomic.api :as d]
|
||||
[clojure.tools.nrepl.server :refer [start-server stop-server]]
|
||||
(:require [clojure.tools.nrepl.server :refer [start-server stop-server]]
|
||||
[mount.core :as mount :refer [defstate]]
|
||||
[app.utils.datomic :refer [touch]]
|
||||
[app.config :refer [app-config]]
|
||||
[app.nyse :as nyse]))
|
||||
[app.www])
|
||||
(:gen-class)) ;; for -main / uberjar (no need in dev)
|
||||
|
||||
;; example on creating a network REPL
|
||||
(defn- start-nrepl [{:keys [host port]}]
|
||||
|
|
@ -14,52 +13,6 @@
|
|||
(defstate nrepl :start (start-nrepl (:nrepl app-config))
|
||||
:stop (stop-server nrepl))
|
||||
|
||||
;; datomic schema
|
||||
(defn create-schema [conn]
|
||||
(let [schema [{:db/id #db/id [:db.part/db]
|
||||
:db/ident :order/symbol
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true
|
||||
:db.install/_attribute :db.part/db}
|
||||
|
||||
{:db/id #db/id [:db.part/db]
|
||||
:db/ident :order/bid
|
||||
:db/valueType :db.type/bigdec
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db.install/_attribute :db.part/db}
|
||||
|
||||
{:db/id #db/id [:db.part/db]
|
||||
:db/ident :order/qty
|
||||
:db/valueType :db.type/long
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db.install/_attribute :db.part/db}
|
||||
|
||||
{:db/id #db/id [:db.part/db]
|
||||
:db/ident :order/offer
|
||||
:db/valueType :db.type/bigdec
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db.install/_attribute :db.part/db}]]
|
||||
|
||||
@(d/transact conn schema)))
|
||||
|
||||
(defn add-order [ticker bid offer qty] ;; can take connection as param
|
||||
@(d/transact nyse/conn [{:db/id (d/tempid :db.part/user)
|
||||
:order/symbol ticker
|
||||
:order/bid bid
|
||||
:order/offer offer
|
||||
:order/qty qty}]))
|
||||
|
||||
|
||||
(defn find-orders [ticker] ;; can take connection as param
|
||||
(let [orders (d/q '[:find ?e :in $ ?ticker
|
||||
:where [?e :order/symbol ?ticker]]
|
||||
(d/db nyse/conn) ticker)]
|
||||
(touch nyse/conn orders)))
|
||||
|
||||
(defn create-nyse-schema []
|
||||
(create-schema nyse/conn))
|
||||
|
||||
;; example of an app entry point
|
||||
(defn -main [& args]
|
||||
(mount/start))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
(ns app.config
|
||||
(:require [mount.core :as mount :refer [defstate]]
|
||||
(:require [mount.core :refer [defstate]]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.tools.logging :refer [info]]))
|
||||
|
||||
|
|
|
|||
50
test/app/db.clj
Normal file
50
test/app/db.clj
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
(ns app.db
|
||||
(:require [mount.core :refer [defstate]]
|
||||
[datomic.api :as d]
|
||||
[clojure.tools.logging :refer [info]]
|
||||
[app.config :refer [app-config]]))
|
||||
|
||||
(defn- new-connection [conf]
|
||||
(info "conf: " conf)
|
||||
(let [uri (get-in conf [:datomic :uri])]
|
||||
(info "creating a connection to datomic:" uri)
|
||||
(d/create-database uri)
|
||||
(d/connect uri)))
|
||||
|
||||
(defn disconnect [conf conn]
|
||||
(let [uri (get-in conf [:datomic :uri])]
|
||||
(info "disconnecting from " uri)
|
||||
(.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))
|
||||
|
||||
;; datomic schema (staging as an example)
|
||||
(defn create-schema [conn]
|
||||
(let [schema [{:db/id #db/id [:db.part/db]
|
||||
:db/ident :order/symbol
|
||||
:db/valueType :db.type/string
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db/index true
|
||||
:db.install/_attribute :db.part/db}
|
||||
|
||||
{:db/id #db/id [:db.part/db]
|
||||
:db/ident :order/bid
|
||||
:db/valueType :db.type/bigdec
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db.install/_attribute :db.part/db}
|
||||
|
||||
{:db/id #db/id [:db.part/db]
|
||||
:db/ident :order/qty
|
||||
:db/valueType :db.type/long
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db.install/_attribute :db.part/db}
|
||||
|
||||
{:db/id #db/id [:db.part/db]
|
||||
:db/ident :order/offer
|
||||
:db/valueType :db.type/bigdec
|
||||
:db/cardinality :db.cardinality/one
|
||||
:db.install/_attribute :db.part/db}]]
|
||||
|
||||
@(d/transact conn schema)))
|
||||
|
|
@ -1,21 +1,20 @@
|
|||
(ns app.nyse
|
||||
(:require [mount.core :as mount :refer [defstate]]
|
||||
[datomic.api :as d]
|
||||
[clojure.tools.logging :refer [info]]
|
||||
[app.config :refer [app-config]]))
|
||||
(:require [datomic.api :as d]
|
||||
[app.db :refer [create-schema] :as db]
|
||||
[app.utils.datomic :refer [touch]]))
|
||||
|
||||
(defn- new-connection [conf]
|
||||
(info "conf: " conf)
|
||||
(let [uri (get-in conf [:datomic :uri])]
|
||||
(info "creating a connection to datomic:" uri)
|
||||
(d/create-database uri)
|
||||
(d/connect uri)))
|
||||
(defn add-order [ticker bid offer qty] ;; can take connection as param
|
||||
@(d/transact db/conn [{:db/id (d/tempid :db.part/user)
|
||||
:order/symbol ticker
|
||||
:order/bid bid
|
||||
:order/offer offer
|
||||
:order/qty qty}]))
|
||||
|
||||
(defn disconnect [conf conn]
|
||||
(let [uri (get-in conf [:datomic :uri])]
|
||||
(info "disconnecting from " uri)
|
||||
(.release conn) ;; usually it's not released, here just to illustrate the access to connection on (stop)
|
||||
(d/delete-database uri)))
|
||||
(defn find-orders [ticker] ;; can take connection as param
|
||||
(let [orders (d/q '[:find ?e :in $ ?ticker
|
||||
:where [?e :order/symbol ?ticker]]
|
||||
(d/db db/conn) ticker)]
|
||||
(touch db/conn orders)))
|
||||
|
||||
(defstate conn :start (new-connection app-config)
|
||||
:stop (disconnect app-config conn))
|
||||
(defn create-nyse-schema []
|
||||
(create-schema db/conn))
|
||||
|
|
|
|||
37
test/app/www.clj
Normal file
37
test/app/www.clj
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
(ns app.www
|
||||
(:require [app.nyse :refer [add-order find-orders create-nyse-schema]]
|
||||
[app.config :refer [app-config]]
|
||||
[mount.core :refer [defstate]]
|
||||
[cheshire.core :refer [generate-string]]
|
||||
[compojure.core :refer [routes defroutes GET POST]]
|
||||
[compojure.handler :as handler]
|
||||
[ring.adapter.jetty :refer [run-jetty]]))
|
||||
|
||||
(defroutes mount-example-routes
|
||||
|
||||
(GET "/" [] "welcome to mount sample app!")
|
||||
(GET "/nyse/orders/:ticker" [ticker]
|
||||
(generate-string (find-orders ticker)))
|
||||
|
||||
(POST "/nyse/orders" [ticker qty bid offer]
|
||||
(add-order ticker (bigdec bid) (bigdec offer) (Integer/parseInt qty))
|
||||
(generate-string {:added {:ticker ticker
|
||||
:qty qty
|
||||
:bid bid
|
||||
:offer offer}})))
|
||||
|
||||
(defn start-nyse [{:keys [www]}]
|
||||
(create-nyse-schema) ;; creating schema (usually done long before the app is started..)
|
||||
(-> (routes mount-example-routes)
|
||||
(handler/site)
|
||||
(run-jetty {:join? false
|
||||
:port (:port www)})))
|
||||
|
||||
(defn resume-nyse [conf]
|
||||
;; making decision to whether call start / do something ro resume / or just do nothing
|
||||
;; ...
|
||||
)
|
||||
|
||||
(defstate nyse-app :start (start-nyse app-config)
|
||||
:resume (resume-nyse app-config)
|
||||
:stop (.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
(ns check.parts-test
|
||||
(:require [mount.core :as mount :refer [defstate] :as m]
|
||||
[app.nyse :refer [conn]]
|
||||
[clojure.test :refer :all]))
|
||||
|
||||
(defstate should-not-start :start (constantly 42))
|
||||
|
||||
(defn with-parts [f]
|
||||
(m/start #'app.config/app-config #'app.nyse/conn)
|
||||
(f)
|
||||
(m/stop))
|
||||
|
||||
(use-fixtures :each with-parts)
|
||||
|
||||
(deftest start-only-parts
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (instance? mount.core.NotStartedState should-not-start)))
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
(ns check.start-with-test
|
||||
(:require [mount.core :as mount :refer [defstate]]
|
||||
[app.config :refer [app-config]]
|
||||
[app.nyse :refer [conn]]
|
||||
[app :refer [nrepl]]
|
||||
[clojure.test :refer :all]))
|
||||
|
||||
(defstate test-conn :start (long 42)
|
||||
:stop (constantly 0))
|
||||
|
||||
(defstate test-nrepl :start (vector))
|
||||
|
||||
(deftest start-with
|
||||
|
||||
(testing "should start with substitutes"
|
||||
(let [_ (mount/start-with {#'app.nyse/conn #'check.start-with-test/test-conn
|
||||
#'app/nrepl #'check.start-with-test/test-nrepl})]
|
||||
(is (map? app-config))
|
||||
(is (vector? nrepl))
|
||||
(is (= conn 42))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should not start the substitute itself"
|
||||
(let [_ (mount/start-with {#'app.nyse/conn #'check.start-with-test/test-conn})]
|
||||
(is (instance? mount.core.NotStartedState test-conn))
|
||||
(is (= conn 42))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should start normally after start-with"
|
||||
(let [_ (mount/start)]
|
||||
(is (map? app-config))
|
||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (= test-conn 42))
|
||||
(is (vector? test-nrepl))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should start-without normally after start-with"
|
||||
(let [_ (mount/start-without #'check.start-with-test/test-conn
|
||||
#'check.start-with-test/test-nrepl)]
|
||||
(is (map? app-config))
|
||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (instance? mount.core.NotStartedState test-conn))
|
||||
(is (instance? mount.core.NotStartedState test-nrepl))
|
||||
(mount/stop))))
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
(ns check.start-without-test
|
||||
(:require [mount.core :as m]
|
||||
[app.config :refer [app-config]]
|
||||
[app.nyse :refer [conn]]
|
||||
[app :refer [nrepl]]
|
||||
[clojure.test :refer :all]))
|
||||
|
||||
(defn without [f]
|
||||
(m/start-without #'app.nyse/conn #'app/nrepl)
|
||||
(f)
|
||||
(m/stop))
|
||||
|
||||
(use-fixtures :each without)
|
||||
|
||||
(deftest start-without-states
|
||||
(is (map? app-config))
|
||||
(is (instance? mount.core.NotStartedState nrepl))
|
||||
(is (instance? mount.core.NotStartedState conn)))
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
(ns check.stop-except-test
|
||||
(:require [mount.core :as mount :refer [defstate]]
|
||||
[app.config :refer [app-config]]
|
||||
[app.nyse :refer [conn]]
|
||||
[app :refer [nrepl]]
|
||||
[clojure.test :refer :all]))
|
||||
|
||||
(deftest stop-except
|
||||
|
||||
(testing "should stop all except nrepl"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/stop-except #'app.nyse/conn #'app.config/app-config)]
|
||||
(is (map? app-config))
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (instance? mount.core.NotStartedState nrepl))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should start normally after stop-except"
|
||||
(let [_ (mount/start)]
|
||||
(is (map? app-config))
|
||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should stop all normally after stop-except"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/stop-except #'app.nyse/conn #'app.config/app-config)
|
||||
_ (mount/stop)]
|
||||
(is (instance? mount.core.NotStartedState app-config))
|
||||
(is (instance? mount.core.NotStartedState conn))
|
||||
(is (instance? mount.core.NotStartedState nrepl)))))
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
(ns check.suspend-resume-test
|
||||
(:require [mount.core :as mount :refer [defstate]]
|
||||
[app.config :refer [app-config]]
|
||||
[app.nyse :refer [conn]]
|
||||
[app :refer [nrepl]]
|
||||
[clojure.test :refer :all]))
|
||||
|
||||
(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))
|
||||
|
||||
(deftest suspendable
|
||||
|
||||
;; lifecycle
|
||||
|
||||
(testing "should suspend _only suspendable_ states that are currently started"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)]
|
||||
(is (map? app-config))
|
||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (= web-server :w-suspended))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should resume _only suspendable_ states that are currently suspended"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/stop #'app/nrepl)
|
||||
_ (mount/suspend)
|
||||
_ (mount/resume)]
|
||||
(is (map? app-config))
|
||||
(is (instance? mount.core.NotStartedState nrepl))
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (= 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? app-config))
|
||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (= 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 app-config))
|
||||
(is (instance? mount.core.NotStartedState nrepl))
|
||||
(is (instance? mount.core.NotStartedState conn))
|
||||
(is (instance? mount.core.NotStartedState web-server))))
|
||||
|
||||
;; start-with
|
||||
|
||||
(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 {#'app/nrepl #'check.suspend-resume-test/web-server})
|
||||
_ (mount/suspend)]
|
||||
(is (= nrepl :w-suspended))
|
||||
(is (instance? mount.core.NotStartedState web-server))
|
||||
(mount/stop)
|
||||
(mount/start)
|
||||
(mount/suspend)
|
||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
||||
(is (= 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 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 {#'check.suspend-resume-test/web-server #'app.nyse/conn}) ;; TODO: good to WARN on started states during "start-with"
|
||||
_ (mount/suspend)]
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (instance? datomic.peer.LocalConnection web-server))
|
||||
(mount/stop)
|
||||
(mount/start)
|
||||
(mount/suspend)
|
||||
(is (instance? datomic.peer.LocalConnection conn))
|
||||
(is (= 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 {#'check.suspend-resume-test/web-server #'check.suspend-resume-test/q-listener})] ;; TODO: good to WARN on started states during "start-with"
|
||||
(is (= q-listener :q-suspended))
|
||||
(is (= web-server :q-resumed))
|
||||
(mount/suspend)
|
||||
(is (= q-listener :q-suspended))
|
||||
(is (= web-server :q-suspended))
|
||||
(mount/stop)
|
||||
(is (instance? mount.core.NotStartedState web-server))
|
||||
(is (instance? mount.core.NotStartedState q-listener))
|
||||
(mount/start)
|
||||
(mount/suspend)
|
||||
(is (= q-listener :q-suspended))
|
||||
(is (= web-server :w-suspended))
|
||||
(mount/stop))))
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
{:datomic
|
||||
{:uri "datomic:mem://mount"}
|
||||
|
||||
:www {:port 4242}
|
||||
|
||||
:h2
|
||||
{:classname "org.h2.Driver"
|
||||
:subprotocol "h2"
|
||||
|
|
|
|||
Loading…
Reference in a new issue