{"id":19345927,"url":"https://github.com/tolitius/yurt","last_synced_at":"2025-04-23T04:36:36.813Z","repository":{"id":62435249,"uuid":"50677911","full_name":"tolitius/yurt","owner":"tolitius","description":"high quality mounted real (e)states","archived":false,"fork":false,"pushed_at":"2020-04-29T01:54:41.000Z","size":72,"stargazers_count":55,"open_issues_count":2,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-04-02T08:23:14.320Z","etag":null,"topics":["clojure","state-management"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tolitius.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-01-29T17:13:58.000Z","updated_at":"2025-02-14T17:55:07.000Z","dependencies_parsed_at":"2022-11-01T21:16:33.631Z","dependency_job_id":null,"html_url":"https://github.com/tolitius/yurt","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fyurt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fyurt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fyurt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tolitius%2Fyurt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tolitius","download_url":"https://codeload.github.com/tolitius/yurt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250372499,"owners_count":21419719,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","state-management"],"created_at":"2024-11-10T04:08:22.444Z","updated_at":"2025-04-23T04:36:36.356Z","avatar_url":"https://github.com/tolitius.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# yurt\n\n  module  |  branch  |  status\n----------|----------|----------\n   yurt   | `master` | [![Circle CI](https://circleci.com/gh/tolitius/yurt/tree/master.png?style=svg)](https://circleci.com/gh/tolitius/yurt/tree/master)\n\n[![Clojars Project](http://clojars.org/yurt/latest-version.svg)](http://clojars.org/yurt)\n\n\u003e \u003cimg src=\"doc/img/slack-icon.png\" width=\"30px\"\u003e _any_ questions or feedback: [`#mount`](https://clojurians.slack.com/messages/mount/) clojurians slack channel (or just [open an issue](https://github.com/tolitius/yurt/issues))\n\n- [What is it for?](#what-is-it-for)\n- [Building Yurts](#building-yurts)\n- [Destroying Yurts](#destroying-yurts)\n- [Swapping Alternate Implementations](#swapping-alternate-implementations)\n- [Building Smaller Yurts](#building-smaller-yurts)\n- [Declarative Yurts](#declarative-yurts)\n- [Stop functions](#stop-functions)\n- [Show me](#show-me)\n\n## What is it for?\n\nThis library manages application stateful components by packaging them in isolated local containers. It calls these containers Yurts and makes sure each Yurt has its own state and lifecyle.\n\nThis is useful to\n\n* develop and run tests in the same REPL: by creating `dev` and `test` Yurts and work with them simultaneously\n* safely run tests in parallel without a fear of shared resources: i.e. different DB schemas, files, etc.\n* anything else that requires multiple clones of an application or its parts\n\nYurt relies on [mount](https://github.com/tolitius/mount) to build these standalone containers. It only uses mount to _discover_ component (`defstate`s), learn about components' dependecies and their start and stop functions. When the Yurt is created it is fully _detached_ from Clojure vars: it becomes a standalone map of components.\n\nMultiple brand new _local_ Yurts with components can be created and passed down to the application / REPL to be used _simultaneously_ in the same Clojure runtime for fun and profit.\n\n## Building Yurts\n\nBesides adding Yurt as a project dependency (boot / lein), nothing else needs to be done to an existing mount application to build Yurts for it.\n\nBefore building local Yurts based on a mount application a \"blueprint\" needs to be created. Blueprint is data _about_ components that were discovered, i.e. not the actual components:\n\n```clojure\ndev=\u003e (yurt/blueprint)\n{:components\n {\"neo.conf/config\" {:status :not-started},\n  \"neo.db/db\" {:status :not-started},\n  \"neo.www/neo-app\" {:status :not-started},\n  \"neo.app/nrepl\" {:status :not-started}},\n :blueprint\n {\"neo.conf/config\" {:order 1},\n  \"neo.db/db\" {:order 2},\n  \"neo.www/neo-app\" {:order 3},\n  \"neo.app/nrepl\" {:order 4}}}\n```\n\nSince Yurt builds upon the knowledge that mount has about an application, this blueprint merely reflects that knowledge.\n\nNow, based on this blueprint, we can build a local Yurt that would have real, \"started\" components:\n\n```clojure\ndev=\u003e (def bp (yurt/blueprint))\ndev=\u003e (def dev-yurt (yurt/build bp))\n```\n\nA `dev-yurt` is built and ready to roll:\n\n```clojure\ndev=\u003e dev-yurt\n{:components\n {\"neo.conf/config\"\n  {:datomic {:uri \"datomic:mem://yurt\"},\n   :www {:port 4242},\n   :nrepl {:host \"0.0.0.0\", :port 7878}},\n  \"neo.db/db\"\n  {:conn\n   #object[datomic.peer.LocalConnection 0x3f66af9c \"datomic.peer.LocalConnection@3f66af9c\"],\n   :uri \"datomic:mem://yurt\"},\n  \"neo.www/neo-app\"\n  #object[org.eclipse.jetty.server.Server 0x2dd20d61 \"org.eclipse.jetty.server.Server@2dd20d61\"],\n  \"neo.app/nrepl\"\n  #clojure.tools.nrepl.server.Server{:server-socket #object[java.net.ServerSocket 0x3ebc5516 \"ServerSocket[addr=/0.0.0.0,localport=7878]\"], :port 7878, :open-transports #object[clojure.lang.Atom 0x7026e6db {:status :ready, :val #{}}], :transport #object[clojure.tools.nrepl.transport$bencode 0x38a2d586 \"clojure.tools.nrepl.transport$bencode@38a2d586\"], :greeting nil, :handler #object[clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__1707 0x3c114a1 \"clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__1707@3c114a1\"], :ss #object[java.net.ServerSocket 0x3ebc5516 \"ServerSocket[addr=/0.0.0.0,localport=7878]\"]}},\n :blueprint\n {\"neo.conf/config\" {:order 1},\n  \"neo.db/db\" {:order 2},\n  \"neo.www/neo-app\" {:order 3},\n  \"neo.app/nrepl\" {:order 4}}}\n```\n\n## Destroying Yurts\n\n```clojure\n(yurt/destroy dev-yurt)\n```\n\nwill stop all the Yurt components using their `:stop` functions defined in `defstate`s.\n\n## Swapping Alternate Implementations\n\nWhile having a development Yurt in the REPL, it might be useful to create a test Yurt _in the same REPL_ to run tests against it while developing.\n\nUsually a test Yurt would be started with a different configuration so, for example, dev and test HTTP server components can run simultaneously on different ports.\n\nThis can be done with the `:swap` option:\n\n```clojure\n(require '[clojure.edn :as edn])\n(def test-config (edn/read-string (slurp \"dev/resources/test-config.edn\")))\n```\n\n```clojure\n(def test-yurt (yurt/build (yurt/blueprint) \n                           {:swap {\"neo.conf/config\" test-config}}))\n```\n\n`:swap` takes a map of component names and their substitutes. In this case `test-config` is a substitute for the value of `\"neo.conf/config\"` component.\n\n## Building Smaller Yurts\n\nA Yurt can be built with `only` certain components specified with an `:only` option:\n\n```clojure\ndev=\u003e (def bp (yurt/blueprint))\n\ndev=\u003e (def small-yurt (yurt/build (yurt/blueprint)\n                                  {:only #{\"neo.conf/config\"\n                                           \"neo.app/nrepl\"}}))\n\nINFO  utils.logging - \u003e\u003e starting.. #'neo.conf/config\nINFO  neo.conf - loading config from dev/resources/config.edn\nINFO  utils.logging - \u003e\u003e starting.. #'neo.app/nrepl\n#'dev/small-yurt\ndev=\u003e\n```\n\n`:only` option that takes a sequence of component names to start, or in other words, components that this Yurt should be built from. In this case `#{\"neo.conf/config\" \"neo.app/nrepl\"}`.\n\nHere is what the built Yurt looks like:\n\n```clojure\ndev=\u003e (pprint small-yurt)\n{:components\n {\"neo.conf/config\"\n  {:datomic {:uri \"datomic:mem://yurt\"},\n   :www {:port 4242},\n   :nrepl {:host \"0.0.0.0\", :port 7878}},\n  \"neo.app/nrepl\"\n  #clojure.tools.nrepl.server.Server{:server-socket #object[java.net.ServerSocket 0x7e5ffa33 \"ServerSocket[addr=/0.0.0.0,localport=7878]\"], :port 7878, :open-transports #object[clojure.lang.Atom 0x6d7364 {:status :ready, :val #{}}], :transport #object[clojure.tools.nrepl.transport$bencode 0x7a4c511f \"clojure.tools.nrepl.transport$bencode@7a4c511f\"], :greeting nil, :handler #object[clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__2241 0x3ca87a46 \"clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__2241@3ca87a46\"], :ss #object[java.net.ServerSocket 0x7e5ffa33 \"ServerSocket[addr=/0.0.0.0,localport=7878]\"]}},\n :blueprint\n {\"neo.conf/config\" {:order 1},\n  \"neo.db/db\" {:order 2},\n  \"neo.www/neo-app\" {:order 3},\n  \"neo.app/nrepl\" {:order 4}}}\n```\n\ni.e. only `\"neo.conf/config\"` and `\"neo.app/nrepl\"` are the components of this Yurt.\n\nand when this yurt is destroyed:\n```clojure\ndev=\u003e (yurt/destroy small-yurt)\n{:stopped #{\"neo.app/nrepl\"}}\n```\n\nonly the components that were part of the Yurt were stopped. In this case `\"neo.app/nrepl\"`,\nsince `\"neo.conf/config\"` does not have a \"`:stop`\" function.\n\n## Declarative Yurts\n\nYurts can be configured declaritively with both a `:swap` map and an `:only` sequence:\n\n```clojure\ndev=\u003e (def test-yurt (yurt/build (yurt/blueprint)\n                                 {:swap {\"neo.conf/config\" test-config}\n                                  :only [\"neo.conf/config\" \"neo.db/db\"]}))\n```\n\nwhich would not start `neo.www/neo-app` _and_ would swap config with a test one:\n\n```clojure\ndev=\u003e ((-\u003e test-yurt :components) \"neo.www/neo-app\")\nnil\n```\n\n```clojure\ndev=\u003e ((-\u003e test-yurt :components) \"neo.conf/config\")\n{:datomic {:uri \"datomic:mem://test-yurt\"},\n :www {:port 4200},\n :nrepl {:host \"0.0.0.0\", :port 7800}}\n```\n\n## Stop functions\n\nThe 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.\n\nFor example:\n\n```clojure\n(defstate nrepl :start (start-nrepl (:nrepl config))\n                :stop stop-server)\n```\n\nwhere `stop-server` is `clojure.tools.nrepl.server/stop-server` function that takes a server to stop.\n\nIn case a stop function needs to take _more_ than one argument it could just take a map. Here is [an example](https://github.com/tolitius/yurt/blob/632ce37f7fb11fbc1c0a0dfc76e47305c954d77d/dev/clj/neo/db.clj#L11-L24).\n\nOne arity `:stop` functions is _the only_ requirement Yurt has for the mount app.\n\n## Show me\n\nsure.\n\n```shell\n$ boot repl\n```\n\n```clojure\nboot.user=\u003e (dev)\n#object[clojure.lang.Namespace 0x61647fa2 \"dev\"]\n```\n\nWorking with a [neo](dev/clj/neo) `mount` sample app that comes with Yurt sources and has 4 components (`mount` states):\n\n* `config`, loaded from the files and refreshed on each (reset)\n* `datomic connection` that uses the config to create itself\n* `nyse web app` which is a web server with compojure routes (i.e. the actual app)\n* `nrepl` that uses config to bind to host/port\n\nFirst before building Yurts, let's checkout the blueprint:\n\n```clojure\ndev=\u003e (yurt/blueprint)\n{:components\n {\"neo.conf/config\" {:status :not-started},\n  \"neo.db/db\" {:status :not-started},\n  \"neo.www/neo-app\" {:status :not-started},\n  \"neo.app/nrepl\" {:status :not-started}},\n :blueprint\n {\"neo.conf/config\" {:order 1},\n  \"neo.db/db\" {:order 2},\n  \"neo.www/neo-app\" {:order 3},\n  \"neo.app/nrepl\" {:order 4}}}\n```\n\nNow let's build a dev Yurt that's based on [this](dev/resources/config.edn) config:\n\n```clojure\ndev=\u003e (def dev-yurt (yurt/build (yurt/blueprint)))\nINFO  neo.conf - loading config from dev/resources/config.edn\nINFO  neo.db - conf:  {:datomic {:uri datomic:mem://yurt}, :www {:port 4242}, :nrepl {:host 0.0.0.0, :port 7878}}\nINFO  neo.db - creating a connection to datomic: datomic:mem://yurt\n#'dev/dev-yurt\n```\n\nnotice the config ports an datomic uri ^^^.\n\nLet's look at what we've built:\n\n```clojure\ndev=\u003e dev-yurt\n{:components\n {\"neo.conf/config\"\n  {:datomic {:uri \"datomic:mem://yurt\"},\n   :www {:port 4242},\n   :nrepl {:host \"0.0.0.0\", :port 7878}},\n  \"neo.db/db\"\n  {:conn\n   #object[datomic.peer.LocalConnection 0x3f66af9c \"datomic.peer.LocalConnection@3f66af9c\"],\n   :uri \"datomic:mem://yurt\"},\n  \"neo.www/neo-app\"\n  #object[org.eclipse.jetty.server.Server 0x2dd20d61 \"org.eclipse.jetty.server.Server@2dd20d61\"],\n  \"neo.app/nrepl\"\n  #clojure.tools.nrepl.server.Server{:server-socket #object[java.net.ServerSocket 0x3ebc5516 \"ServerSocket[addr=/0.0.0.0,localport=7878]\"], :port 7878, :open-transports #object[clojure.lang.Atom 0x7026e6db {:status :ready, :val #{}}], :transport #object[clojure.tools.nrepl.transport$bencode 0x38a2d586 \"clojure.tools.nrepl.transport$bencode@38a2d586\"], :greeting nil, :handler #object[clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__1707 0x3c114a1 \"clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__1707@3c114a1\"], :ss #object[java.net.ServerSocket 0x3ebc5516 \"ServerSocket[addr=/0.0.0.0,localport=7878]\"]}},\n :blueprint\n {\"neo.conf/config\" {:order 1},\n  \"neo.db/db\" {:order 2},\n  \"neo.www/neo-app\" {:order 3},\n  \"neo.app/nrepl\" {:order 4}}}\n```\n\nNow let's build a test (second) Yurt based on [this](dev/resources/test-config.edn) test config:\n\n```clojure\n;; reading the test config in..\ndev=\u003e (require '[clojure.edn :as edn])\ndev=\u003e (def test-config (edn/read-string (slurp \"dev/resources/test-config.edn\")))\n#'dev/test-config\n```\n\nnotice we are building it by the same blueprint:\n\n```clojure\ndev=\u003e (def test-yurt (yurt/build (yurt/blueprint) {:swap {\"neo.conf/config\" test-config}}))\nINFO  neo.db - conf:  {:datomic {:uri datomic:mem://test-yurt}, :www {:port 4200}, :nrepl {:host 0.0.0.0, :port 7800}}\nINFO  neo.db - creating a connection to datomic: datomic:mem://test-yurt\n#'dev/test-yurt\n```\n\njust substituting the config component _with_ the test config:\n\n```clojure\n;; e.g. (yurt/build (yurt/blueprint) {:swap {\"neo.conf/config\" test-config}})\n```\n\nnotice the config ports an datomic uri ^^^.\n\nwe can substitute as many components as we want since `:swap` takes a map where keys are the state names, and values are the substitutes (i.e. any values).\n\nLet's look at what we've built:\n\n```clojure\ndev=\u003e test-yurt\n{:components\n {\"neo.conf/config\"\n  {:datomic {:uri \"datomic:mem://test-yurt\"},\n   :www {:port 4200},\n   :nrepl {:host \"0.0.0.0\", :port 7800}},\n  \"neo.db/db\"\n  {:conn\n   #object[datomic.peer.LocalConnection 0x48b2fa4 \"datomic.peer.LocalConnection@48b2fa4\"],\n   :uri \"datomic:mem://test-yurt\"},\n  \"neo.www/neo-app\"\n  #object[org.eclipse.jetty.server.Server 0x77fb2bac \"org.eclipse.jetty.server.Server@77fb2bac\"],\n  \"neo.app/nrepl\"\n  #clojure.tools.nrepl.server.Server{:server-socket #object[java.net.ServerSocket 0xbc92366 \"ServerSocket[addr=/0.0.0.0,localport=7800]\"], :port 7800, :open-transports #object[clojure.lang.Atom 0x1bde6216 {:status :ready, :val #{}}], :transport #object[clojure.tools.nrepl.transport$bencode 0x38a2d586 \"clojure.tools.nrepl.transport$bencode@38a2d586\"], :greeting nil, :handler #object[clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__1707 0x2edf365d \"clojure.tools.nrepl.middleware$wrap_conj_descriptor$fn__1707@2edf365d\"], :ss #object[java.net.ServerSocket 0xbc92366 \"ServerSocket[addr=/0.0.0.0,localport=7800]\"]}},\n :blueprint\n {\"neo.conf/config\" {:order 1},\n  \"neo.db/db\" {:order 2},\n  \"neo.www/neo-app\" {:order 3},\n  \"neo.app/nrepl\" {:order 4}}}\ndev=\u003e\n```\n\nLet's look deep inside the Yurts and see, for example, if their Jetty web servers are running:\n\n```clojure\ndev=\u003e (.isStarted ((-\u003e dev-yurt :components) \"neo.www/neo-app\"))\ntrue\ndev=\u003e (.isStarted ((-\u003e test-yurt :components) \"neo.www/neo-app\"))\ntrue\n```\n\nNow let's destroy the test Yurt:\n\n```clojure\ndev=\u003e (yurt/destroy test-yurt)\nINFO  neo.db - disconnecting from  datomic:mem://test-yurt\n```\n\nCheck the server statuses again:\n\n```clojure\ndev=\u003e (.isStarted ((-\u003e test-yurt :components) \"neo.www/neo-app\"))\nfalse\ndev=\u003e (.isStarted ((-\u003e dev-yurt :components) \"neo.www/neo-app\"))\ntrue\n```\n\nnotice that the test server is no longer running, but the development one is.\n\nLet's destroy the development Yurt as well:\n\n```clojure\ndev=\u003e (yurt/destroy dev-yurt)\nINFO  neo.db - disconnecting from  datomic:mem://yurt\n```\n\nCheck the server statuses again:\n\n```clojure\ndev=\u003e (.isStarted ((-\u003e test-yurt :components) \"neo.www/neo-app\"))\nfalse\ndev=\u003e (.isStarted ((-\u003e dev-yurt :components) \"neo.www/neo-app\"))\nfalse\n```\n\nGreat, we are now ready to build as many _local_, `mount` based Yurts as we'd like and run them _simultaneously_ in the same Clojure runtime.\n\n## License\n\nCopyright © 2018 tolitius\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Fyurt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftolitius%2Fyurt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftolitius%2Fyurt/lists"}