Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Mountup to track ns recompilation #5

Open
Folcon opened this issue Apr 26, 2020 · 8 comments
Open

Using Mountup to track ns recompilation #5

Folcon opened this issue Apr 26, 2020 · 8 comments

Comments

@Folcon
Copy link

Folcon commented Apr 26, 2020

Hey, thanks for mount =)...

I'm trying to work out if you can/should use mountup to track dependent ns recompilations to trigger a restart?

For example, I'm currently using it with pathom, which defines resolvers.

The problem I have is that I want my pathom state to be restarted when I reload any of my resolver namespaces.

Is the way that I'd achieve this with mountup?

One possible way I was thinking of doing it would be to define my all of my resolver vars in the form of a defstate and then use mountup to watch them and reload mount?

Is there a better way? Or perhaps this is inadvisable?

@tolitius
Copy link
Owner

tolitius commented Apr 29, 2020

unfortunately I have not used pathom hence I do not have an understanding of what a resolver in this context is ) is it a multimethod? a macro? a function? is it also a state? does it make sense to have a single state that has a map of these resolvers? what is a pathom state and where is it kept?

to track dependent ns recompilations to trigger a restart

mount does track that and restarts everything in a namespace that was recompiled.

if all your resolvers need to be started and stopped along with pathom state would it not make sense to have start and stop functions of pathom state to do that?

again, this is just with no understanding what the resolver or phatom state/is.

@Folcon
Copy link
Author

Folcon commented Apr 30, 2020

Hi @tolitius,

Sorry I wrote that in a bit of a rush and it's my fault for being super unclear.

I did not mean to imply that you should know anything about pathom =)...

Let me try again.

I have two namespaces:
my resolvers

(ns myapp.resolvers
  (:require [mount.core :refer [defstate]]))

(defstate resolvers
  :start [request-resolver])

pathom

(ns myapp.pathom
  (:require [mount.core :refer [defstate]]
            [com.wsscode.pathom.core :as p]
            [myapp.resolvers :as r])) ;; <-- Importing the namespace

(defn build-parser []
  (p/parser {:resolvers r/resolvers ...}))

(defstate parser
  :start (build-parser))

As you can see resolvers is a defstate, as is parser, how do I setup so that when myapp.resolvers gets recompiled parser in myapp.pathom should also get recompiled?

From your answer it appears that I need to tell mount somewhere explicitly that this is a dependency? Or is this a use-case for mountup? Or something else =)...

@tolitius
Copy link
Owner

tolitius commented May 1, 2020

how are resolvers used?
the reason I am asking is I tend to keep a minimum number of states in apps.
hence wanted to understand whether:

(defn build-parser []
  (let [resolvers ;; build resolvers here]
    (p/parser {:resolvers resolvers ...})))

(defstate parser
  :start (build-parser))

would work without keeping a separate "resolvers" state.
i.e. whoever needs resolvers can do (:resolvers parser)

mount-up could certainly be used (in development mode/profile), or both states can be moved to the same namespace, possibly a separate (not the main) namespace.

But usually, from seeing this uncertainties I tend to search for a slight "redesign", not to avoid the problem, but simply to not have it in the first place.

@Folcon
Copy link
Author

Folcon commented May 1, 2020

Before I started this, I was just putting resolvers in a var, an application can have anything from dozens to hundreds of resolvers, that are all combined into a single root resolver which is the one passed in. However the parser call there compiles all of the defined resolvers for performance, so redefining/reloading them doesn't change anything.

Hence my wanting to have the parser defstate reload whenever any of the possibly many namespaces that contain resolvers are reloaded.

An alternative redesign would be nice, but I'm not sure how to proceed due to the aforementioned compiling that occurs, but placing them in the same namespace might get a bit unwieldy =)...

@tolitius
Copy link
Owner

tolitius commented May 1, 2020

due to the aforementioned compiling that occurs

it does not have to occur if you don't want

the parser call there compiles all of the defined resolvers for performance

is the parser only needed to compile this resolvers for performace? i.e. can you include / build the parser in the resolvers state?

(defn build-resolvers [...]
  ;; build resolvers
  ;; build parser
  {:parser parser
   :resolvers resolvers})

(defstate resolvers :start (builld-resolvers)
                    :stop :stopped)

@Folcon
Copy link
Author

Folcon commented May 4, 2020

I'm being unclear =)...

I'm aware that you can noop or stop mount defstate reload, it's a great feature =)...

The parser function is designed to compile the resolver state whenever it's defined, so altering the resolver definitions doesn't update their behaviour, only redefining the parser does that.

I can and do include the resolver state in the parser definition, however my problem is that when the namespace containing a resolver is reloaded, my parser defstate has no way of knowing it should restart.

After doing some further digging it seems this has been asked before, similar to that example I want to set it up so that whenever I reload a namespace containing a resolver, my parser function is restarted.

I'd prefer to do this without having to add another dependency if at all possible =)... Is there some way to tell mount to watch certain namespaces so that if they are reloaded, a particular defstate also reloads? I'm happy to manually manage this, at least initially =)...

It appears mount does construct some kind of dependency tree?

(require '[mount.tools.graph :as graph])
(graph/states-with-deps)

Hence my asking if I should define all the different resolver functions as defstates, as that would trigger some kind of reload?

An alternative potentially really messy solution where I do this by hand might be:
my resolvers

(ns myapp.resolvers
  (:require [mount.core :refer [defstate]]))

(defn build-resolvers [resolvers]
  (mount/stop '#myapp.pathom/parser)
  (mount/start '#myapp.pathom/parser)
  resolvers)

(defstate resolvers
  :start (build-resolvers [request-resolver]))

pathom

(ns myapp.pathom
  (:require [mount.core :refer [defstate]]
            [com.wsscode.pathom.core :as p]
            [myapp.resolvers :as r]))

(defn build-parser []
  (p/parser {:resolvers r/resolvers ...}))

(defstate parser
  :start (build-parser))

Now if I reload the myapp.resolvers, myapp.pathom/parser should restart.

I'd prefer if I didn't have to define lots of defstates, but I'm just asking if this is the only way to do this?

And thank you for all your help =)...

@tolitius
Copy link
Owner

two more questions )

(defstate resolvers :start [request-resolver])
  1. why does this need to be a separate state?

I can and do include the resolver state in the parser definition, however my problem is that when the namespace containing a resolver is reloaded, my parser defstate has no way of knowing it should restart.

  1. I'd like to better understand what "the namespace containing a resolver" mean.

For example in case you create resolvers inside a let block when building a parser:

(ns parser)

(defn build-parser []
  (let [resolvers ;; build resolvers here]
    (p/parser {:resolvers resolvers ...})))

(defstate parser
  :start (build-parser))

other namespaces may then require parser and get to resolvers:

(ns other-namespace
  (:require [parser :as p]))

(defn i-take-parser [{:keys [resolvers]}]
  (let [{:keys [request]} resolvers]
    ;; ...))

(defn i-am-ok-close-over-parser-state []
  (let [{:keys [request]} (-> p :resolvers :request)]
    ;; ...))

@Folcon
Copy link
Author

Folcon commented May 26, 2020

Hi @tolitius, sorry I haven't responded up until now, been a bit busy 😊...

To answer your questions:

  1. why does this need to be a separate state?

I mean I could rewrite it all into a function call if that would make it better? Is that what you mean? As it stands it was just a var.

A resolver itself is created by a macro provided by the library called defresolver. The pattern they suggest is:

(ns user)

;; Potentially many of these
(defresolver i-resolve-all-users [params]
  ;;...)

(defresolver i-resolve-active-users [params]
  ;;...)

(def resolvers [i-resolve-all-users i-resolve-active-users])
  1. I'd like to better understand what "the namespace containing a resolver" mean.

Let me rewrite my thing into your example, so hopefully it will be clearer =)...

(ns parser
  (:require [session]
            [user]
            [other-business-objects]))

(defn build-parser []
  (let [resolvers [session/resolvers user/resolvers other-business-objects/resolvers]]  ;; <-- It's this bit that's complicated
    (p/parser {:resolvers resolvers ...})))

(defstate parser
  :start (build-parser))
(ns session)

(defresolver i-resolve-all-sessions [params]
  ;;...)

(def resolvers [i-resolve-all-sessions])  ;; <-- should this be a `defstate`?
(ns user)

(defresolver i-resolve-all-users [params]
  ;;...)

(def resolvers [i-resolve-all-users])  ;; <-- should this be a `defstate`?

Now hopefully you see the issue? When I reload user or session or other-business-objects, the parser namespace seems to have no way of knowing that dependent state has changed, so it should also reload...

Perhaps the best that can be done is the build-resolvers function that I wrote?

(defn build-resolvers [resolvers]
  (mount/stop '#parser/parser)
  (mount/start '#parser/parser)
  resolvers)
(ns user
  (:require [utils :refer [build-resolvers]]))

(defn i-resolve-users [params]
  ;;...)

(defstate resolvers
  :start (build-resolvers [i-resolve-users]))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants