Skip to content

Subscriptions RFC: Bi-directional channels between client and server #278

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

Closed
robzhu opened this issue Mar 6, 2017 · 11 comments
Closed

Subscriptions RFC: Bi-directional channels between client and server #278

robzhu opened this issue Mar 6, 2017 · 11 comments

Comments

@robzhu
Copy link
Contributor

robzhu commented Mar 6, 2017

Do we need a "bi-directional" channel between client and server?
@taion @stubailo @OlegIlyenko

Continuing the conversation from: #267 (comment)

@taion
Copy link

taion commented Mar 6, 2017

Copying over my response from #267:

The use of SSE v WS is an application-level implementation detail. All that's needed is the server being able to push messages to the client somehow, which distinguishes this from request/response.

The communication isn't really bi-di anyway; it's a request to make a subscription, then a stream from the server for subscription updates. SSE is perfectly fine for this, assuming other technical concerns permit its use.

@robzhu
Copy link
Contributor Author

robzhu commented Mar 6, 2017

I agree, SSE is fine for pushing messages from the server to the client, but SSE alone is not the full story. Perhaps the wording/semantics could be more explicit-- there had to be a request going from client to server for subscribe and unsubscribe. So we essentially have a logical channel where data flows both ways and can be initiated by either client or server. Whether that channel consists of SSE/WS/web hooks can be an implementation detail.

@taion
Copy link

taion commented Mar 6, 2017

You could picture an SSE-only approach where each new subscription is a request to an HTTP endpoint with SSE.

This wouldn't necessarily be a great way to implement things, but there's nothing that would prevent it from working.

@stubailo
Copy link
Contributor

stubailo commented Mar 6, 2017

The question here is - do subscriptions require a concept of "unsubscribing"? If so, then that implies that there is some state that tells the server whether or not there is a client to send events to. That's my mental model of what "bidirectional communication" means - the server is aware of whether someone is listening.

@taion
Copy link

taion commented Mar 6, 2017

We're probably getting into hermeneutics a bit, then. From @OlegIlyenko's initial post, there's no reason GraphQL subscriptions can't work over SSE instead of over WebSockets. If that's the only question, then I think we're on the same page here.

@OlegIlyenko
Copy link
Contributor

OlegIlyenko commented Mar 6, 2017

I think "bi-directional" is a quite broad term. For instance, I consider UDP to be uni-directional. My server can make a broadcast of data based on a subscription query. But as soon as client somehow tells a server the subscription query (which may also be a human communication), the overall communication flow can be considered bi-directional, IMO.

In my opinion, this is a transport concern, so it would not be right to overspecify it in GraphQL spec.

I would suggest to introduce an abstract notion of "infinite result stream" or "result observable" with following properties:

  • This "result observable" is the result of execution of subscription query in contrast to a single result of normal and mutation queries
  • Observable results are non-deterministically distributed in time. This means that they may not necessarily be emitted at the same time.
  • Result stream is infinite, so it may never naturally "complete".
  • Stream may become exhausted. In this case, it is considered "completed" and it will not emit further results
  • GraphQL execution engine may experience an unrecoverable error. This will "complete" stream as well.
  • Consumer of the stream must be able to perform 2 operations on it:
    • subscribe to results emitted by the stream. Results will come one at a time.
    • Prematurely "complete" the stream

It's just some basic properties of observables as seen in ReactiveX, reactive-streams, etc. I think this can be the right abstraction level that describes a semantics of interaction with GraphQL execution engine but does not imply any particular transport or client-server interaction mechanism.

If we would like to capture the semantics and protocol of subscriptions-transport-ws or similar implementations, I feel it would be better to tackle it as a separate specification (just like relay). I think it's a higher level abstraction and it should be possible to express it on top of the primitives I described above.

What do you think about it?

@robzhu
Copy link
Contributor Author

robzhu commented Mar 6, 2017

@OlegIlyenko, awesome way to break it down.

GraphQL execution engine may experience an unrecoverable error. This will "complete" stream as well.

This is the one point I would object trying to tackle in this issue. I agree with everything else you said. My intent was not to describe specific transport capabilities, merely that the communication flow throughout the subscription lifecycle is bi-directional because there are both client and server initiated communications (in the case of SSE + HTTP, I consider the initial HTTP request to be an instance of client-initiated communication).

I think everyone is mostly in agreement on this topic. The only follow up action is to make the wording more clear. Any objections?

@taion
Copy link

taion commented Mar 7, 2017

None from me; I think the point is that the communication system should in some manner enable the server to push messages to the clients. Whether we call that "bidirectional" is unimportant.

@jamesgorman2
Copy link

I like @OlegIlyenko 's break down, though I think

  • Result stream is infinite, so it may never naturally "complete".
  • Stream may become exhausted. In this case, it is considered "completed" and it will not emit further results
  • GraphQL execution engine may experience an unrecoverable error. This will "complete" stream as well.

is a little unclear. My preference would be for upstream to be able signal no more data so we can close redundant connections if possible (and for downstream to do the same). (It seems like that this the intent here so maybe just a language use issue). Cognate events from Rx are complete, error and unsubscribe which is a nice symmetry.

@OlegIlyenko
Copy link
Contributor

@jamesgorman2 yes, I generally mean the same thing. The completion of a stream is propagated to both the producer and consumer (depending on who initiated it). So that they can do proper cleanup if necessary

@robzhu
Copy link
Contributor Author

robzhu commented Mar 28, 2017

Removed the mention of "bi-directional" with this commit: 96184f0

@robzhu robzhu closed this as completed Mar 28, 2017
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

5 participants