Heya guys .. i’m curious how people manage the ordering dependencies when using mount. For example, if I want to setup a websocket connection on mount/start, but other people need access to that connection when it becomes established, what’s a good way to handle this?
I cannot simply do defstate conn :start do-connection
and expect something in another namespace to be able to conn (:register #(… ))
for example. Given the way namespaces are loaded, I cannot have a ‘top level’ / ‘raw’ function that’s invoked immediately and expect conn to be available.
Is there a way to hook into a “the :start of this state is complete” -> now do something with state?
Not without defining another defstate
.
My impression is that you're basically just not supposed to do that.
I figured that might be the case.. i will have to come up with some other mechanism I suppose
I guess my question would be: Why are you trying to do that?
Having things other than definitions at the "top level" is generally discouraged.
Imagine I only want one ws connection from my client -> server.. but many features might have to subscribe to various channels on that connection’s session. I don’t want a giant list of “when the connection is made hook up all these features”, but instead want the features to somehow be notified of the connection.
The more I type the more I realize this has nothing to do with mount, and I was just hoping mount might make the solution easier than it really is.
I'm not understanding why the features that use the connection wouldn't either be functions or other defstate
s.
They would, but figuring out a decoupled way to “hand the session over" to them and guarantee bootstrapping order, etc. is the real problem.
But that’s not anything to do with mount, as I mentioned
It kind of does.
If it were me I would have those features take the connection as an argument, and have some defstate
call them with it as necessary.
Hope that's helpful.
Ill think on it, thanks
@zane: I think I understand now. I can still guarantee ordering by making sure the feature that wants to use the connection also registers with mount.
(defstate core :start #(ws-namespace/ws-conn-state register-myself))
Where ws-conn-state
might return an api as opposed to data.For each feature that registers I maintain a seq of registerees, and upon a successful connection, let each one know.
Sure. That's one way to do it.
Or you could do it the other way around where the features are defstate
s and refer directly to the defstate
with the connection in it.
I could, but that seems more coupling to me. I would prefer N unknown features requiring a connection, unless I’m misunderstanding you.
Hmm, doesnt seem like defstates are meant to ever be changed beyond :start? I can’t swap!
into one of them.
@lwhorton: can you have many conns in the application?
with mount it's expected that you declare your dependencies via require
s, what zane proposed:
(ns consumer
(:require [ws-connection :as ws]))
(defstate consumer :start (create-state ws/conn))
(defn do-something [] (... consumer ...))
if you have just one connection - this is not more coupled than having the consumer accept the connection as a parameter IMOThat’s how I decided to run with it ^
@lwhorton: I was thinking about it.. https://github.com/tolitius/mount/issues/16 the simple solution has not come to me yet, but it will 🙂
for now, you can definitely do what @zane and @dm3 suggest: i.e. establish implicit dependencies via :require
another way you can think of approaching it would be running:
(mount/start #'ws/conn)
(mount/start)
which would start the connection before starting anything else, and then would bootstrap the appoh that’s a neat idea
I dont mind ‘implicit dependencies’ as you say because, well, i dont think they’re that implicit
imo its no different than any other IoC I might write that does something like [:inject foo bar]
i guess you don’t get the luxury of other neat-o things like startable
and other runtime-pre-start configuration options
what do you mean by startable
?
its a common pattern in most inversion of control containers — my favorite being https://github.com/castleproject/Windsor/blob/master/docs/README.md
it’s just a means to allow dynamic at-runtime configuration.. you can declare things like mixins, overrides, startable/stoppable, decorators, etc.
I haven’t found something similar in clojureland , but I also haven’t had the need (yet)
I'd be interested to hear whether you wind up needing them.
My suspicion is Clojure's philosophy on mutable state will obviate the need for most.
more than likely, but when you start talking about client-side guis where there is a lot of potential for reuse I found things like “on the fly” decorators to be amazing
dynamic at-runtime configuration
, in your use case (with ws conn
), can you share how you would solve it with a startable
?
sure, a fun lib I use in jsland https://github.com/mnichols/ankh provides an async startable implementation-
in this sense, the ioc worries about making sure everything is started and “ready to go” and is pretty orthogonal to your application code
@tolitius: ^ (and sorry about the javascript)
@lwhorton: what bothers you if the above is expressed as
; websocket.clj
(ns websocket)
(defstate socket :start ...)
; impl.clj
(ns impl (:require [websocket :as ws]))
(defstate impl :start (gogo ws/socket))
?
Just trying to understand the motivation behind the "injection" part