lein-ring icon indicating copy to clipboard operation
lein-ring copied to clipboard

Reloading with component *without* introducing global state

Open codeasone opened this issue 8 years ago • 6 comments

Firstly, thanks for your amazing work for the Clojure community.

I've been using lein-ring for some time now and in particular the auto-reload facility which I find very helpful.

However, I'm just getting started with component, and have reached an impasse where I cannot see a way of getting the reloading benefits of lein ring without introducing global state.

I've created a minimal re-production of where I have got to and was hoping someone could advise on whether what I'm aiming for is possible.

https://github.com/codeasone/component-reload-issue

codeasone avatar Jul 05 '17 11:07 codeasone

You don't want to use Lein-Ring with Component (except possibly for creating war files). Instead, prefer the reloaded workflow pattern, perhaps with a library such as reloaded.repl.

weavejester avatar Jul 05 '17 15:07 weavejester

I should have provided a little more context concerning my particular motivation and use-case.

Whilst I like the reloaded workflow in cider - based on a variation of Stuart Sierra's user.clj - I also have a use-case where there's a bunch of micro-services starting in a Docker composition with command: lein ring server-headless

This has been working tremendously well over the past couple of months for me, prior to seeking a dependency-injection solution to improve my tests/mocking.

With host volume mounts to the various projects, when I change the code for any service in the composition, lein-ring takes care of reloading, allowing me to execute integration-level and exploratory tests as I go (no restarts really helps with momentum).

I suppose I could introduce something "Heath Robinson" like a /reset route for each of those services and building some tooling in Emacs to poke each /reset end-point on saves, but I'd rather not.

I've just finished porting the project over to mount which is behaving well so far, I may just keep going on that line of work if I cannot find another solution.

codeasone avatar Jul 05 '17 15:07 codeasone

Sorry, I'm not completely clear on your use case. Is this Docker composition in a development or production environment? Is there a reason you can't just open an nREPL connection to it?

weavejester avatar Jul 06 '17 00:07 weavejester

I'm the one who should apologise, I should have given the use-case straight away and less ambiguously.

My concern is rapid acceptance-test driven development. I have 8-10 micro-services from our portfolio (if you will) and bring up a docker composition with host volumes mounting each micro-service project directory. I built some tooling called island for this in Ruby actually.

It's been working great, but in my most recent project I want to introduce some proper dependency injection and component looked ideal.

So as you say I could run an nrepl in development mode, but having all the relevant namespaces auto-reload on a save is so effortless I was hoping I could reconcile component with my existing use of lein-ring.

As I mentioned before, mount is proving a better partner and giving me all I need so far.

I suggest we close this issue unless you have a further interest in the approach I'm taking and want to discuss that more.

Many thanks for your time and consideration in reply to the issue.

codeasone avatar Jul 06 '17 08:07 codeasone

Perhaps it would help if I described my typical setup.

I use reloaded.repl, which gives me useful REPL functions like (go) for starting the system and (reset) for suspending the system, reloading all changed files, then resuming the system.

I connect to my application through Cider, and Cider has a useful function called cider-refresh that can integrant the functionality of reset into the editor. To do this, we first need to set "before" and "after" function. I typically do this by putting a .dir-locals.el file in my project directory that contains:

((nil . ((cider-refresh-before-fn . "reloaded.repl/suspend")
         (cider-refresh-after-fn  . "reloaded.repl/resume"))))

In my ~/.emacs/init.el file, I have a small convenience function that combines saving a file and reloading the system in one, when I press ⌘R:

(defun cider-save-and-refresh ()
  (interactive)
  (save-buffer)
  (call-interactively 'cider-refresh))

(global-set-key (kbd "s-r") 'cider-save-and-refresh)

This setup is more nuanced that reloading files on save, as it allows me to save temporary work without messing up the running application. However, if you want to skip the Cider integration, you can still have reset-on-save functionality by hooking up a file watcher to reset.

For example, using Hawk something like this should work:

(hawk.core/watch! [{:paths ["src" "test"], :handler (fn [ctx _] (reset) ctx)}])

weavejester avatar Jul 06 '17 12:07 weavejester

Hmm, the extra control sounds appealing and hawk looks a useful addition.

I'll give that a try over the weekend. Thanks for the advice.

codeasone avatar Jul 06 '17 12:07 codeasone