tolitius / mount

managing Clojure and ClojureScript app state since (reset)
Eclipse Public License 1.0
1.22k stars 88 forks source link

How to update a state with a runtime function invocation result #64

Open OliverM opened 7 years ago

OliverM commented 7 years ago

I see in Mount's examples that a state captures the output of the :start fn and that the :stop fn interacts with that result.

What if the arguments to the :start fn aren't known until runtime? For example, I'd like to pass in a runtime-generated set of routes to my web handler. So if I have the following function to launch a server:

(defn launch-server [routes port] (run-jetty routes {:port port}))

How do I define a state to capture the output of that function at runtime, that I can then use to close the server in my :stop fn?

(defstate http-server :start launch-server :stop ???)

Currently I'm rigging my own solution with an atom but it feels like I'm side-stepping mount rather than using it correctly:

(def server-atom (atom nil))
(defn launch-server [routes port] (reset! server-atom (run-jetty routes {:port port}))
(defstate http-server :start launch-server :stop (.stop @server-atom))
tolitius commented 7 years ago

who and when calls launch-server passing routes and port args?

runtime args in this context look like configuration: i.e. you would usually get a port number from a config file.

following the same mount example you linked to, there is also a config state that http server uses. At runtime config state is created before the webserver, hence it becomes a "runtime argument" for it.

let's looks at this example:

dev=> (def conf {:routes "my routes" :port 4242})
#'dev/conf
dev=>

dev=> (defn launch-server [{:keys [routes port]}] (println "running: " routes {:port port}) {:routes routes :port port})
#'dev/launch-server
dev=>

dev=> (defn stop-http [server] (println "stopping:" server))
#'dev/stop-http
dev=>

dev=> (defstate http-server :start (launch-server conf) :stop (stop-http http-server))
"|| mounting... #'dev/http-server"
#'dev/http-server
dev=>

dev=> (mount/start #'dev/http-server)
running:  my routes {:port 4242}
{:started ["#'dev/http-server"]}

dev=> (mount/stop #'dev/http-server)
stopping: {:routes my routes, :port 4242}
{:stopped ["#'dev/http-server"]}

routes is probably available to reference right from the function (i.e. they don't come from a config, but rather a function that returns routes or a var routes), but a port number comes from the conf.

the difference between the example above and your example is conf that, I suspect, also comes from "somewhere" at runtime in your example, and could be just made into a state.

But of course I'd need more details to understand your use case better.

There are a couple more project examples here you can look at.

OliverM commented 7 years ago

The routes are generated dynamically at runtime; the user passes in a set of data, and routes are generated from that data, and a http server is spun up dynamically to serve those routes. This could happen several times during program execution. Normally the port wouldn't change as each set of routes replaces its predecessor, but there will be some situations where I'll need a couple of sets of routes offered on different ports.

I guess what I was thinking of was some sort of defstate invocation that would set it's state to the result of an invocation of its initial function, but it might be my concept of what runtime configuration is that's at fault here! I like the way mount makes configuration available via clojure namespaces, and so assumed this would be a use-case for it, but if it's purely aimed at configuration in different execution contexts (dev vs testing vs prod etc) then I should probably just use an atom for this case directly. Although I really like using mount to stop & start the server too, rather than having to patch that in myself (plus catching the server in the atom does seem to work fine).

Thanks for the pointers about the config handling in neo & smsio!