Skip to content

Commit

Permalink
#3: scoped Yurts
Browse files Browse the repository at this point in the history
  • Loading branch information
tolitius committed Nov 14, 2016
1 parent 147525f commit 54f0d27
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 16 deletions.
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Multiple brand new _local_ Yurts with components can be created and passed down
- [Building Yurts](#building-yurts)
- [Destroying Yurts](#destroying-yurts)
- [Swapping Alternate Implementations](#swapping-alternate-implementations)
- [Building Smaller Yurts](#building-smaller-yurts)
- [Stop functions](#stop-functions)
- [Show me](#show-me)

Expand Down Expand Up @@ -102,6 +103,53 @@ This can be done with the `(yurt/build-with)` function:

`(build-with)` takes a blueprint and a map where keys are component names, and values are the substitutes (i.e. any values), in this case `test-config` is a substitute.

## Building Smaller Yurts

A Yurt can be built with `only` certain components specified:

```clojure
dev=> (def bp (yurt/blueprint))
dev=> (def dev-yurt (yurt/build-only bp #{"neo.conf/config" "neo.app/nrepl"}))

INFO utils.logging - >> starting.. #'neo.conf/config
INFO neo.conf - loading config from dev/resources/config.edn
INFO utils.logging - >> starting.. #'neo.app/nrepl
#'dev/dev-yurt
dev=>
```

notice it was built with a `build-only` function that takes a blueprint and components
that this Yurt should be built from. In this case `#{"neo.conf/config" "neo.app/nrepl"}`.

Here is what the built Yurt looks like:

```clojure
dev=> (pprint dev-yurt)
{:components
{"neo.conf/config"
{:datomic {:uri "datomic:mem://yurt"},
:www {:port 4242},
:nrepl {:host "0.0.0.0", :port 7878}},
"neo.app/nrepl"
#clojure.tools.nrepl.server.Server{:server-socket #object[java.net.ServerSocket 0x173f7861 "ServerSocket[addr=/0.0.0.0,localport=7878]"], :port 7878, :open-transports #object[clojure.lang.Atom 0x284e5c96 {:status :ready, :val #{}}], :transport #object[clojure.tools.nrepl.transport$bencode 0x78bb7947 "clojure.tools.nrepl.transport$bencode@78bb7947"], :greeting nil, :handler #object[clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__2035 0x72d2a9bf "clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__2035@72d2a9bf"], :ss #object[java.net.ServerSocket 0x173f7861 "ServerSocket[addr=/0.0.0.0,localport=7878]"]}},
:blueprint
{"neo.conf/config" {:order 1},
"neo.db/db" {:order 2},
"neo.www/neo-app" {:order 3},
"neo.app/nrepl" {:order 4}}}
```

i.e. only `"neo.conf/config"` and `"neo.app/nrepl"` are the components of this Yurt.

and when this yurt is destroyed:
```clojure
dev=> (yurt/destroy dev-yurt)
{:stopped #{"neo.app/nrepl"}}
```

only the components that were part of the Yurt were stopped. In this case `"neo.app/nrepl"`,
since `"neo.conf/config"` does not have a stop function.

## Stop functions

The only thing Yurt requires from `defstate`s is that their `:stop` functions are 1 arity (i.e. take one argument). When the `(yurt/destroy)` is called, it would pass the actual instance of a component to this `:stop` function.
Expand Down
45 changes: 30 additions & 15 deletions src/yurt/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
(build [this] "builds a hut")
(destroy [this] "destroys a hut")
(build-with [this substitutes] "builds a hut with some components substituted")
(build-only [this components] "builds a hut out of only components provided")
;; (build-without [this components])
;; (destroy-except [this components])
)
Expand All @@ -19,10 +20,15 @@
[name fun])))))

(defn- bulldoze [components funs]
(into {}
(doseq [[name fun] funs]
((fun) (components name))
[name :stopped])))
(reduce (fn [cs [name fun]]
(let [component (components name)]
(if (and component ;; only destroy existing components
(not= component :not-started)) ;; that were started
(do ((fun) component)
(conj cs name))
cs)))
#{}
funs))

(defn- unvar-state [s]
(->> s (drop 2) (apply str))) ;; magic 2 is removing "#'" in state name
Expand All @@ -44,22 +50,30 @@
(detach-state state status)
(#'mount.core/rollback! state)))

(defn- attach [sys]
(into {}
(for [[k {:keys [var]}] sys]
[(unvar-state k) @var])))
(defn- var-comp [xs]
(into #{} (map var-state xs)))

(defn- attach [sys & {:keys [only]}]
(let [in-scope? (var-comp only)
no-scope? (empty? in-scope?)]
(into {}
(for [[k {:keys [var]}] sys]
(when (or no-scope?
(in-scope? k))
[(unvar-state k) @var])))))

(defn- var-subs [m]
(into {}
(for [[k v] m]
[(var-state k) v])))

;; TODO: this will take with / without / individual states
(defn- spawn [sys & {:keys [swap]}]
(if-not swap
(mount/start)
(mount/start-with (var-subs swap)))
(let [spawned (attach @sys)]
;; TODO: combinations of only/swap/etc..
(defn- spawn [sys & {:keys [swap only] :as ops}]
(condp some (keys ops)
#{:swap} (mount/start-with (var-subs swap))
#{:only} (mount/start (var-comp only))
(mount/start))
(let [spawned (attach @sys :only only)]
(detach @sys)
spawned))

Expand All @@ -84,5 +98,6 @@
Hut
(build [_] (->Yurt (spawn meta-state) bp))
(build-with [_ substitutes] (->Yurt (spawn meta-state :swap substitutes) bp))
(destroy [it] (bulldoze (:components it) stop-fns)))
(build-only [_ components] (->Yurt (spawn meta-state :only components) bp))
(destroy [it] {:stopped (bulldoze (:components it) stop-fns)}))
(->Yurt (not-started states) bp)))
18 changes: 17 additions & 1 deletion test/yurt/test/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,20 @@

(yurt/destroy a-yurt)
(is (not (.isStarted a-www)))
(is (not (.isStarted b-www))))))
(is (not (.isStarted b-www)))))

(testing "yurt can be build with a specified scope of components"
(let [blue (yurt/blueprint)
yurt (yurt/build-only blue #{"neo.conf/config" "neo.app/nrepl"})
www ((-> yurt :components) "neo.www/neo-app")
conf ((-> yurt :components) "neo.conf/config")
db ((-> yurt :components) "neo.db/db")
repl ((-> yurt :components) "neo.app/nrepl")]

(is (not www))
(is (not db))
(is conf)
(is (not (.isClosed (:server-socket repl))))

(is (= (yurt/destroy yurt) {:stopped #{"neo.app/nrepl"}}))
(is (.isClosed (:server-socket repl))))))

0 comments on commit 54f0d27

Please sign in to comment.