No, but afaik it's something you can do yourself. @dominicm might be able to help more
It may be achievable by using profiles perhaps
Yeah, good point, it might be possible to reorganize my Aero config to achieve what I'm looking for. I have a feeling that might be a bit clunky, but maybe I'll give it a go.
Also, this might be more of a general Clojure question than a Clip-specific question, but: Given a Ring handler like this:
(def app (wrap-middleware my-handler))
To have your changes take effect without having to restart the HTTP server, you can pass the handler var to http-kit like this:
(org.httpkit.server/run-server #'app options)
Now, I can just re-evaluate (def app ,,,)
and my changes take effect.
However, with Clip, I have something like this in my Aero config:
:handler {:start (app.server.handler/app {:db (clip/ref :db)})}
:http {:start (org.httpkit.server/run-server (clip/ref :handler) {:port 1337})
:stop (this)}
Now, re-evaluating app.server.handler/app
is no longer enough to make my changes live. I have to do a whole-app reset with clojure.tools.namespace.repl.
I haven't figured out a way to arrange things such that I can just re-evaluate my handler and have my changes take effect immediately. Any thoughts?This is something I want to investigate further, but it's a pitfall shared with integrant and component. I will add that the performance benchmarks I've done indicate there's no significant difference between using the handler directly, and "creating" the handler on every request.
Interesting, thanks! With the amount of traffic my apps get, that might indeed be an option. I also tried hacking together a solution with alter-var-root
, but I couldn't make it work.
I haven't used Component, but Integrant does indeed suffer from the same problem.
Maybe there's a way to use profiles to recreate the handler function on every request in dev but not in prod.
You could do this:
(defn app [& args]
(if (:direct-linking *compiler-options*)
(wrap-middleware (apply my-handler args))
(fn [req]
((wrap-middleware (apply my-handler args)) req))))
And something could potentially automate that for you tooOr maybe even (defn app [{:keys [re-eval?] :as opts}] (if re-eval? (fn …) (wrap-middleware …)))
https://cljdoc.org/d/metosin/reitit/0.5.12/doc/advanced/dev-workflow#an-easy-fix basically this I guess, dev-router and prod-router :)
Yeah, that's probably the way to go. I'm always a bit wary of code that works differently in dev and prod, though. 🙂
I just hacked together a proof of concept where clip can reconstruct your function every time it's called. It's a little bit funky (it calls your code, does a fn?
check, then decides to rewrap it in that case).
This might be a good use-case for custom lifecycles (as that's how I hacked it in, it runs after :start
and detects the fn and replaces it in that case. It might also be a good use-case for some kind of explicit transformation system.
Looks very cool! I'll definitely give it a try next week at work.
Noticed a typo: relodable-fn
. Other than that, the reloading part seems to work great. :thumbsup::skin-tone-2: I think this is a hugely useful feature.
However, calling juxt.clip.repl/stop
fails with "Unable to evaluate form (#'org.httpkit.server/run-server #object[juxt.clip.repl$relodable_fn$reloadable_wrapper__1914 0x33590414 \"juxt.clip.repl$relodable_fn$reloadable_wrapper__1914@33590414\"] {:port 7070})"
with that SHA.
I'm probably doing something wrong. I don't quite understand what you mean by this:
> you'll need to make sure they're not coming from edn as symbols (e.g. by just assoc it on in your code portion).
All I did is updated deps.edn
to use that SHA and added this into my Aero config: :reloads #profile {:dev {clojure.lang.Fn juxt.clip.repl/relodable-fn}}
That error sounds familiar, but I don't remember why 🙂
Very odd that it seems to be running your start function on stop
> you'll need to make sure they're not coming from edn as symbols (e.g. by just assoc it on in your code portion). ^ this isn't relevant with the later sha I think 🙂
Yeah, I'll try to find the time to dive into the problem next week.
Ah, I see the problem
http-kit returns a function, so clip is trying to reload it for you :)
@flowthing This solved it:
(def other-system-config
{:components
{:handler `{:start (handler {})}
:http '{:start (org.httpkit.server/run-server (clip/ref :handler) {:port 8080})
:stop (this)}}
:reloads
`{clojure.lang.Fn repl/relodable-fn
:http nil}})
Ah, of course! Thanks, I’ll try that out.
When this hits master, API might actually be more like:
(def other-system-config
{:components
{:handler `{:start (handler {})}
:http '{:start (org.httpkit.server/run-server (clip/ref :handler) {:port 8080})
:stop (this)
:reload nil}}
:reloads
`{clojure.lang.Fn repl/relodable-fn}})
As I think it's important to localize how the reload works. On the fence about whether matchers should be predicate based or some such.Could definitely see some kind of "convention" where a team does:
(def other-system-config
{:components
{:handler/a `{:start (handler {})}
:handler/b `{:start (handler {})}
:router '{:start [["/about" (clip/ref :handler/a)]
["/foobar" (clip/ref :handler/b)]]}
:http '{:start (org.httpkit.server/run-server (clip/ref :handler) {:port 8080})
:stop (this)
:reload nil}}
:reloads
{(fn [k _v] (= "handler" (namespace k))) repl/relodable-fn}})
General solution is good, but API is still open for debate.
Could also add some data to the exception when this happens too, and fall back onto the original. Not sure if stop should operate on the original item or the wrapped one either…
@flowthing Your configuration looks like good for me. You can check my following repo for which eloaded works: https://github.com/PrestanceDesign/todo-backend-clojure-reitit/tree/clip Maybe it can help.
As far as I can tell, in that example, to have changes to your handler or router take effect, you have to call (user/reset)
, which is what I want to avoid.
OK I will better read your request. 👍