taoensso / sente

Realtime web comms library for Clojure/Script
https://www.taoensso.com/sente
Eclipse Public License 1.0
1.74k stars 196 forks source link

FR: Separate out transport messages from user-land messages #89

Closed yatesco closed 10 years ago

yatesco commented 10 years ago

Having used sente for all of 2 hours :), I am missing an abstraction of "transport messages". I want to implement a protocol/listen to a separate channel for messages like:

And have a completely separate mechanism for sending user-land messages (which can still check for communication errors but don't have to.

At the moment they are all lumped into the same channel and I end up with a generic "send" mechanism which takes the message. I could use core.async splitters (or whatever they are caleld) to good use here.

Ideally I want to create the sente plumbing and at that point provide a mechanism for being alerted about the health of the transport.

I am claiming that the meta-data about the connection currently happens to be implemented through magic event ids, but that is an implementation concern. They are two separate concerns/abstractions and the API should treat them as such (even if the API also provides a "health-listener" which puts the messages onto the "consumer channel" to emulate current behaviour).

Just a thought - thanks!

ptaoussanis commented 10 years ago

Hi Colin!

I want to implement a protocol/listen to a separate channel for messages like

Have you looked at the multimethod-based event dispatch in the reference example? If so, could you expand a little on what you find lacking in that approach?

I am claiming that the meta-data about the connection currently happens to be implemented through magic event ids, but that is an implementation concern.

I wouldn't really call these "magic" event ids, or an implementation concern - any Sente event ids that you'll encounter on the Sente event channel are part of the public API, stable, and are there for you to act on or ignore as you like. They're all namespaced as :chsk/, so easy to ignore/redirect if you like.

Does that help / make sense?

Cheers! :-)

yatesco commented 10 years ago

(I mentioned in the other thread) I am seeing sente as a low-level "solve the nasties around the transport" mechanism. I am not seeing it as an "application" building block, more a low-level "kernel" building block so I wouldn't want any of my application code receiving sente events directly.

What I have ended up doing is (in terms of listening to sente):

In other words, a trivial facade around sente.

The other side of the plumbing provides the protocol implementation - the error/recovery messages simply update a "server-healthy?" flag in my om app-state, every other application message is dispatched to the rest of the app accordingly (typically as you say via multimethods).

In terms of writing to sente, the plumbing listens to a global channel for an application event, which is then put throw over to sente.

This means my plumbing/integration layer provides two channels, one for receiving application messages and one for sending application messages to the server, any communication errors are handled in the plumbing and not in the main application and the rest of the application gets to talk its own language.

The choice of protocol over a channel for consuming sente events is really one of style, but I liked the abstraction of on-init, on-disconnect etc.

To be honest, the implementation is much less than this explanation :), and I am not that wedded to it but I do like the following properties:

In a nutshell, I am after the separation of concerns between how we communicate with the server and why/what we communicate with the server.

As I said, it is trivial to add this sort of thing on top of sente, and maybe the need I am feeding is one from decades of Java (i.e. boilerplate/over-engineering :)).

ptaoussanis commented 10 years ago

Appreciate the clarification.

My 2c would be to try stick with the suggested multimethod-against-event-id dispatch mechanism for a while and see what actual problems you run into in practice. There's only 3 real pieces of information intrinsically relevant to each event: its data, its source, and its type (some kind of routing info).

Since we have namespaced keywords, the last two can conveniently be captured in a single value (the event-id).

Any other machinery that you put in place (e.g. splitting event streams into different source channels, wrapping things in protocols etc.) is orthogonal to the actual data flowing through the system. You'd be putting distance between yourself and the data (values).

Now you can certainly make decisions there based on taste, or what shape you'd like your application to be - but I'd suggest that if you're seeing the default approach as somehow limiting and/or leaky, it's possible that there might be something off in the way you're picturing it playing out.

Remember that multimethods can be defined in any namespace, can have defaults, and can dispatch on any arbitrary function of events.

Just to clarify this point again: any :chsk/ events you see are not implementation details, but stable API-level events that applications will often want to respond to. The connection going down is an application-level event just as much as a button click is. It's a real event, it happened, it has relevance to the application layer. Different parts of your application logic may want to handle that event, but that's what the multimethods are for.

Does that make any sense? Sente operates at a relatively high level out-the-box- going any higher might end up biting you in unexpected ways later, so I'd only do it after careful consideration of what you'd be getting in return.

In any case, what matters most is that you're satisfied with the solution you have. Just providing some (subjective) pointers in case they turn out to be helpful.

Good luck with your project, cheers! :-)