Fork me on GitHub
#mount
<
2016-05-20
>
lwhorton16:05:43

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?

lwhorton16:05:07

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.

lwhorton16:05:38

Is there a way to hook into a “the :start of this state is complete” -> now do something with state?

zane16:05:35

Not without defining another defstate.

zane16:05:48

My impression is that you're basically just not supposed to do that.

lwhorton16:05:08

I figured that might be the case.. i will have to come up with some other mechanism I suppose

zane16:05:21

I guess my question would be: Why are you trying to do that?

zane16:05:46

Having things other than definitions at the "top level" is generally discouraged.

lwhorton16:05:47

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.

lwhorton16:05:05

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.

zane16:05:09

I'm not understanding why the features that use the connection wouldn't either be functions or other defstates.

lwhorton16:05:07

They would, but figuring out a decoupled way to “hand the session over" to them and guarantee bootstrapping order, etc. is the real problem.

lwhorton16:05:19

But that’s not anything to do with mount, as I mentioned

zane16:05:31

It kind of does.

zane16:05:12

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.

zane16:05:18

Hope that's helpful.

lwhorton16:05:37

Ill think on it, thanks

lwhorton16:05:34

@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.

lwhorton16:05:35

For each feature that registers I maintain a seq of registerees, and upon a successful connection, let each one know.

zane16:05:14

Sure. That's one way to do it.

zane16:05:56

Or you could do it the other way around where the features are defstates and refer directly to the defstate with the connection in it.

lwhorton16:05:48

I could, but that seems more coupling to me. I would prefer N unknown features requiring a connection, unless I’m misunderstanding you.

lwhorton17:05:22

Hmm, doesnt seem like defstates are meant to ever be changed beyond :start? I can’t swap! into one of them.

dm317:05:40

@lwhorton: can you have many conns in the application?

dm317:05:51

with mount it's expected that you declare your dependencies via requires, 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 IMO

lwhorton18:05:41

That’s how I decided to run with it ^

tolitius18:05:03

@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 app

lwhorton18:05:53

oh that’s a neat idea

lwhorton18:05:08

I dont mind ‘implicit dependencies’ as you say because, well, i dont think they’re that implicit

lwhorton18:05:31

imo its no different than any other IoC I might write that does something like [:inject foo bar]

lwhorton18:05:57

i guess you don’t get the luxury of other neat-o things like startable and other runtime-pre-start configuration options

tolitius18:05:08

what do you mean by startable?

lwhorton18:05:27

its a common pattern in most inversion of control containers — my favorite being https://github.com/castleproject/Windsor/blob/master/docs/README.md

lwhorton18:05:11

it’s just a means to allow dynamic at-runtime configuration.. you can declare things like mixins, overrides, startable/stoppable, decorators, etc.

lwhorton18:05:32

I haven’t found something similar in clojureland , but I also haven’t had the need (yet)

zane18:05:31

I'd be interested to hear whether you wind up needing them.

zane18:05:59

My suspicion is Clojure's philosophy on mutable state will obviate the need for most.

lwhorton18:05:59

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

tolitius18:05:53

dynamic at-runtime configuration, in your use case (with ws conn), can you share how you would solve it with a startable?

lwhorton18:05:05

sure, a fun lib I use in jsland https://github.com/mnichols/ankh provides an async startable implementation-

lwhorton18:05:23

in this sense, the ioc worries about making sure everything is started and “ready to go” and is pretty orthogonal to your application code

lwhorton18:05:48

@tolitius: ^ (and sorry about the javascript)

dm321:05:45

@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