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

[RFC] GraphQL Subscriptions #267

Merged
merged 7 commits into from Mar 7, 2017

Conversation

Projects
None yet
@robzhu
Contributor

robzhu commented Feb 16, 2017

Let's add GraphQL Subscriptions to the Spec!

Show outdated Hide outdated rfcs/Subscriptions.md
**Possible Solutions**
We broadly categorize realtime API solutions into three types:

This comment has been minimized.

@syrusakbary

syrusakbary Feb 16, 2017

Not sure if is a typo, but there are six types defined below (instead of the three mentioned here).

@syrusakbary

syrusakbary Feb 16, 2017

Not sure if is a typo, but there are six types defined below (instead of the three mentioned here).

This comment has been minimized.

@stubailo

stubailo Feb 16, 2017

Contributor

(If you look at the rendered view, it's a nested list)

@stubailo

stubailo Feb 16, 2017

Contributor

(If you look at the rendered view, it's a nested list)

@stubailo

This comment has been minimized.

Show comment
Hide comment
@stubailo

stubailo Feb 16, 2017

Contributor

This is really great! It was awesome that this took into account feedback from a variety of sources.

I think this sketch of the subscriptions lifecycle is compatible with the one currently implemented in our graphql-subscriptions package here, which can be a nice way to test-drive some of the concepts: https://github.com/apollographql/graphql-subscriptions/

We've designed it in such a way that anyone currently using GraphQL.js can just drop it in and start using subscriptions right away, and we have some adapters for Redis, MQTT, etc.

We'll make sure that implementation is always up to date as new conclusions are reached in the discussion. After all, this RFC is just the beginning, not something set in stone.

There's also a transport protocol over websockets (something that shouldn't be covered in a spec, but you need a transport to use it) which we are currently using in production. It isn't coupled to any particular client or web server package, and implements all of the important lifecycle events necessary: https://github.com/apollographql/subscriptions-transport-ws

I encourage people interested in this proposal to try out those packages with their existing server and experiment with GraphQL subscriptions today! I think that's going to be a great way to collect more feedback about this design.

Contributor

stubailo commented Feb 16, 2017

This is really great! It was awesome that this took into account feedback from a variety of sources.

I think this sketch of the subscriptions lifecycle is compatible with the one currently implemented in our graphql-subscriptions package here, which can be a nice way to test-drive some of the concepts: https://github.com/apollographql/graphql-subscriptions/

We've designed it in such a way that anyone currently using GraphQL.js can just drop it in and start using subscriptions right away, and we have some adapters for Redis, MQTT, etc.

We'll make sure that implementation is always up to date as new conclusions are reached in the discussion. After all, this RFC is just the beginning, not something set in stone.

There's also a transport protocol over websockets (something that shouldn't be covered in a spec, but you need a transport to use it) which we are currently using in production. It isn't coupled to any particular client or web server package, and implements all of the important lifecycle events necessary: https://github.com/apollographql/subscriptions-transport-ws

I encourage people interested in this proposal to try out those packages with their existing server and experiment with GraphQL subscriptions today! I think that's going to be a great way to collect more feedback about this design.

@leebyron

This comment has been minimized.

Show comment
Hide comment
@leebyron

leebyron Feb 16, 2017

Collaborator

Awesome! Here's a rough schedule of what happens next:

This is a really well written RFC, but this should not deter anyone from asking questions - both getting into the weeds and challenging core assumptions and aspects of the proposal. An RFC is the beginning of a discussion. Let's leave this PR open as a place for discussion for the time being.

Collaborator

leebyron commented Feb 16, 2017

Awesome! Here's a rough schedule of what happens next:

This is a really well written RFC, but this should not deter anyone from asking questions - both getting into the weeds and challenging core assumptions and aspects of the proposal. An RFC is the beginning of a discussion. Let's leave this PR open as a place for discussion for the time being.

@OlegIlyenko

This comment has been minimized.

Show comment
Hide comment
@OlegIlyenko

OlegIlyenko Feb 16, 2017

Really happy to see progress on subscriptions! Good job, it looks great so far! 🏆

the input query and variables are mapped to a set of events

It would be interesting to see the semantics of this mapping. In particular, do the root subscription fields have any special semantics or the interpretation of the root field semantics can be defined by a GraphQL server (for example, does every root field represents specific type of event like commentCreated or topic comments or something else)?

Also another remark. The RFC assumes and emphasizes that subscription communication is bi-directional. I wonder whether this restriction is necessary or required. I definitely see appeal of having bi-directional communication. For instance, it can multiplex several subscriptions in a single WebSocket connection and provide extended life-cycle phases for further optimizations.

But in the most basic form, uni-directional communication (server → client) can be sufficient to implement semantics described in the RFC. A while back I implemented an example GraphQL subscription service that is based on SSE, which is uni-directional. As far as I can tell, it satisfies the semantics described in this RFC, but simplifies life-cycle a bit:

  • Subscribe (client → server) - represented by server receiving a client request with subscription query
  • Subscription active (server → client) - absent/implicit
  • Unsubscribe (client → server) - client just closes the connection

Would love to hear your thoughts on this aspect of the RFC :)

Also regarding bi-directional communication. As far as I can tell, it defines a small protocol - something similar to apollo WS transport protocol. Does this mean that in future we will have additional specification, alongside of GraphQL, that will define and standardize this bi-directional communication protocol (life-cycle phases, data format, command types like SUBSCRIPTION_START, SUBSCRIPTION_FAIL, UNSUBSCRIBE, etc.)?

OlegIlyenko commented Feb 16, 2017

Really happy to see progress on subscriptions! Good job, it looks great so far! 🏆

the input query and variables are mapped to a set of events

It would be interesting to see the semantics of this mapping. In particular, do the root subscription fields have any special semantics or the interpretation of the root field semantics can be defined by a GraphQL server (for example, does every root field represents specific type of event like commentCreated or topic comments or something else)?

Also another remark. The RFC assumes and emphasizes that subscription communication is bi-directional. I wonder whether this restriction is necessary or required. I definitely see appeal of having bi-directional communication. For instance, it can multiplex several subscriptions in a single WebSocket connection and provide extended life-cycle phases for further optimizations.

But in the most basic form, uni-directional communication (server → client) can be sufficient to implement semantics described in the RFC. A while back I implemented an example GraphQL subscription service that is based on SSE, which is uni-directional. As far as I can tell, it satisfies the semantics described in this RFC, but simplifies life-cycle a bit:

  • Subscribe (client → server) - represented by server receiving a client request with subscription query
  • Subscription active (server → client) - absent/implicit
  • Unsubscribe (client → server) - client just closes the connection

Would love to hear your thoughts on this aspect of the RFC :)

Also regarding bi-directional communication. As far as I can tell, it defines a small protocol - something similar to apollo WS transport protocol. Does this mean that in future we will have additional specification, alongside of GraphQL, that will define and standardize this bi-directional communication protocol (life-cycle phases, data format, command types like SUBSCRIPTION_START, SUBSCRIPTION_FAIL, UNSUBSCRIBE, etc.)?

@stubailo

This comment has been minimized.

Show comment
Hide comment
@stubailo

stubailo Feb 16, 2017

Contributor

@OlegIlyenko my interpretation is that "bidirectional" means that in your description there are arrows that go from client -> server and also server -> client.

Contributor

stubailo commented Feb 16, 2017

@OlegIlyenko my interpretation is that "bidirectional" means that in your description there are arrows that go from client -> server and also server -> client.

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 16, 2017

Aren't any client -> server legs just mutations?

This looks consistent with what's implemented between relay-subscriptions and graphql-relay-subscription as well.

A few comments:

taion commented Feb 16, 2017

Aren't any client -> server legs just mutations?

This looks consistent with what's implemented between relay-subscriptions and graphql-relay-subscription as well.

A few comments:

@stubailo

This comment has been minimized.

Show comment
Hide comment
@stubailo

stubailo Feb 16, 2017

Contributor

We started our implementation without a "subscription active" notification, but later had to add it. Because the first result from your subscription could arrive a long time after the initial request, you don't know if you're actually receiving those updates. In some cases, you want to be able to look at the state of the subscription to decide if you should be polling for data or simply waiting for events to arrive, especially if you don't want to miss some crucial updates to the information you're looking for.

Essentially, it can be very hard to know what happened if you subscribe and get back total silence - did the subscription somehow fail or stall, or did it successfully start and you should expect to get results in the future? @taion I think you're using socket.io right? Maybe they have a system that checks if the subscription is successful under the hood?

Contributor

stubailo commented Feb 16, 2017

We started our implementation without a "subscription active" notification, but later had to add it. Because the first result from your subscription could arrive a long time after the initial request, you don't know if you're actually receiving those updates. In some cases, you want to be able to look at the state of the subscription to decide if you should be polling for data or simply waiting for events to arrive, especially if you don't want to miss some crucial updates to the information you're looking for.

Essentially, it can be very hard to know what happened if you subscribe and get back total silence - did the subscription somehow fail or stall, or did it successfully start and you should expect to get results in the future? @taion I think you're using socket.io right? Maybe they have a system that checks if the subscription is successful under the hood?

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 16, 2017

@stubailo

To clarify, I'm saying that "subscription active" notifications aren't required in all cases.

It really depends on the app. If it's something like chat, then "subscriptions are down" is a big deal and you want to show some user feedback. For something like, say, showing "new comment" prompts on a GitHub issue page, you don't really care.

A general purpose subscription layer probably wants to expose something like those "subscription active" notifications to handle all uses cases, but they're not always necessary in more specialized cases.

taion commented Feb 16, 2017

@stubailo

To clarify, I'm saying that "subscription active" notifications aren't required in all cases.

It really depends on the app. If it's something like chat, then "subscriptions are down" is a big deal and you want to show some user feedback. For something like, say, showing "new comment" prompts on a GitHub issue page, you don't really care.

A general purpose subscription layer probably wants to expose something like those "subscription active" notifications to handle all uses cases, but they're not always necessary in more specialized cases.

@stubailo

This comment has been minimized.

Show comment
Hide comment
@stubailo

stubailo Feb 16, 2017

Contributor

Good call - I agree that it is not necessary in cases where the subscription is not providing critical functionality. I think one other factor to consider is that returning an error about the initial subscription definitely is necessary - for example, if the query is not valid at all you want to receive some validation error in response to your initial subscription. Without a message that there was a successful subscription (essentially it just tells you the query validated etc) there's this time when you're not sure if there will be an initial error or not.

Contributor

stubailo commented Feb 16, 2017

Good call - I agree that it is not necessary in cases where the subscription is not providing critical functionality. I think one other factor to consider is that returning an error about the initial subscription definitely is necessary - for example, if the query is not valid at all you want to receive some validation error in response to your initial subscription. Without a message that there was a successful subscription (essentially it just tells you the query validated etc) there's this time when you're not sure if there will be an initial error or not.

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 16, 2017

Yup – error handling is necessary in general. I guess it depends how in-detail this spec wants to go on what the network layer should do.

taion commented Feb 16, 2017

Yup – error handling is necessary in general. I guess it depends how in-detail this spec wants to go on what the network layer should do.

@deanius

This comment has been minimized.

Show comment
Hide comment
@deanius

deanius Feb 16, 2017

Questions like whether a subscription can do events singly or must be multiple, as asked in the blog post, point to the possbible usefulness of an abstraction like Observable, which is not inherently tied to multiplicity of responses. Also, that it is moving through standardization in the language would be helpful too.

I'm also a fan of the DDP 'ready' event that announces the change from serving up existing old data to serving up new responses. While not strictly necessary, it lets you do things like remove loading graphics as you already know quite well @stubailo :)

My 2c on unsubscribe is it should be a message as well (as in DDP), but obviously the server should do the right thing if a connection goes stale and no unsubscribe was received.

PS I'm in the category of someone whose org may adopt GraphQL if it had subscriptions. Right now we're rolling something on Meteor DDP until we see if Apollo could give us a compatible-enough experience.

deanius commented Feb 16, 2017

Questions like whether a subscription can do events singly or must be multiple, as asked in the blog post, point to the possbible usefulness of an abstraction like Observable, which is not inherently tied to multiplicity of responses. Also, that it is moving through standardization in the language would be helpful too.

I'm also a fan of the DDP 'ready' event that announces the change from serving up existing old data to serving up new responses. While not strictly necessary, it lets you do things like remove loading graphics as you already know quite well @stubailo :)

My 2c on unsubscribe is it should be a message as well (as in DDP), but obviously the server should do the right thing if a connection goes stale and no unsubscribe was received.

PS I'm in the category of someone whose org may adopt GraphQL if it had subscriptions. Right now we're rolling something on Meteor DDP until we see if Apollo could give us a compatible-enough experience.

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 16, 2017

Observable is really a JS-level implementation detail, no? I don't think it's pertinent at the level of this spec.

taion commented Feb 16, 2017

Observable is really a JS-level implementation detail, no? I don't think it's pertinent at the level of this spec.

@rmosolgo

This comment has been minimized.

Show comment
Hide comment
@rmosolgo

rmosolgo Feb 16, 2017

Thanks for writing this up, I'm looking forward to seeing how it evolves! I had a couple questions:

  • Somehow, I was previously under the impression that subscription requests also returned an immediate result, as if they were queries. That's not mentioned here, right? So I was mistaken about that?

  • Would it make sense to add one more arrow to this diagram? I thought there might be one like this:

    image

    After the subscription system is notified of an event, it tells the GraphQL server to re-evaluate the query, and the GraphQL server gives a new response (in magenta) to the subscription system. (Then, the subscription system sends it to the client over the particular transport layer.) Did I understand correctly? That's how I made sense of this arrow in the later diagram:

    image

rmosolgo commented Feb 16, 2017

Thanks for writing this up, I'm looking forward to seeing how it evolves! I had a couple questions:

  • Somehow, I was previously under the impression that subscription requests also returned an immediate result, as if they were queries. That's not mentioned here, right? So I was mistaken about that?

  • Would it make sense to add one more arrow to this diagram? I thought there might be one like this:

    image

    After the subscription system is notified of an event, it tells the GraphQL server to re-evaluate the query, and the GraphQL server gives a new response (in magenta) to the subscription system. (Then, the subscription system sends it to the client over the particular transport layer.) Did I understand correctly? That's how I made sense of this arrow in the later diagram:

    image

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 16, 2017

I don't think in general subscription queries should return an immediate result. If I have a subscription on e.g. items being added, what would that result be?

taion commented Feb 16, 2017

I don't think in general subscription queries should return an immediate result. If I have a subscription on e.g. items being added, what would that result be?

@deanius

This comment has been minimized.

Show comment
Hide comment
@deanius

deanius Feb 17, 2017

@taion - The point of it wasn't to prescribe an implementation, but to convey semantics by referring to an existing spec to broaden the discussion. The contract covers issues like you mentioned above such as whether you get a result upon subscribe. http://reactivex.io/documentation/contract.html

deanius commented Feb 17, 2017

@taion - The point of it wasn't to prescribe an implementation, but to convey semantics by referring to an existing spec to broaden the discussion. The contract covers issues like you mentioned above such as whether you get a result upon subscribe. http://reactivex.io/documentation/contract.html

@smolinari

This comment has been minimized.

Show comment
Hide comment
@smolinari

smolinari Feb 17, 2017

Whatever the solution may be. I 👍 the addition to the spec, because without a subscription system, GraphQL can hardly be considered as "state of the art", when if fact, it really should be.

Edit: Had to also link to Sashko's great blog post.

Scott

smolinari commented Feb 17, 2017

Whatever the solution may be. I 👍 the addition to the spec, because without a subscription system, GraphQL can hardly be considered as "state of the art", when if fact, it really should be.

Edit: Had to also link to Sashko's great blog post.

Scott

@viniciussbs viniciussbs referenced this pull request Feb 17, 2017

Closed

GraphQL Subscriptions #156

@theorygeek

This comment has been minimized.

Show comment
Hide comment
@theorygeek

theorygeek Feb 17, 2017

Contributor

One thing that's not necessarily clear to me (and to be honest, I haven't been down in the trenches with subscriptions, unlike others commenting here) is this:

When the client wants to create a subscription, is it just executing a GraphQL query with a special operation? Or is it sending a package of "something" (including a GraphQL query, variables, etc) to something else, and then that something else is interacting with GraphQL?

I was previously under the impression that if I sent this query to my GraphQL server, using the exact same transport/etc mechanics as any other query, that it was creating a subscription:

subscription {
  someField {
    # sub-selections
  }
}

In this scenario, the GraphQL library (graphql-ruby in my case) is handling the "book-keeping" of the subscription, and somehow I'm plugging in:

  • Events
  • A special network layer

But after reading this spec, it sounds like I'm instead directing this to something other than my GraphQL library, handling the book-keeping myself, and periodically running a not-so-special query and sending the (black box) payload to the client.

Essentially, what I'm getting at, is should we expect authors of GraphQL frameworks to be providing the book-keeping for subscriptions, etc and then exposing some fancy integration points (events and networking)? Or should we expect a new eco-system of subscription frameworks to grow up alongside existing GraphQL frameworks?

Contributor

theorygeek commented Feb 17, 2017

One thing that's not necessarily clear to me (and to be honest, I haven't been down in the trenches with subscriptions, unlike others commenting here) is this:

When the client wants to create a subscription, is it just executing a GraphQL query with a special operation? Or is it sending a package of "something" (including a GraphQL query, variables, etc) to something else, and then that something else is interacting with GraphQL?

I was previously under the impression that if I sent this query to my GraphQL server, using the exact same transport/etc mechanics as any other query, that it was creating a subscription:

subscription {
  someField {
    # sub-selections
  }
}

In this scenario, the GraphQL library (graphql-ruby in my case) is handling the "book-keeping" of the subscription, and somehow I'm plugging in:

  • Events
  • A special network layer

But after reading this spec, it sounds like I'm instead directing this to something other than my GraphQL library, handling the book-keeping myself, and periodically running a not-so-special query and sending the (black box) payload to the client.

Essentially, what I'm getting at, is should we expect authors of GraphQL frameworks to be providing the book-keeping for subscriptions, etc and then exposing some fancy integration points (events and networking)? Or should we expect a new eco-system of subscription frameworks to grow up alongside existing GraphQL frameworks?

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 17, 2017

I imagine implementation-wise there's not much actual change, right? As set up right now, GraphQL.js can execute a subscription query, but the actual semantics of the full request live outside. It's like the split between graphql and express-graphql for the standard query/mutation side.

taion commented Feb 17, 2017

I imagine implementation-wise there's not much actual change, right? As set up right now, GraphQL.js can execute a subscription query, but the actual semantics of the full request live outside. It's like the split between graphql and express-graphql for the standard query/mutation side.

@stubailo

This comment has been minimized.

Show comment
Hide comment
@stubailo

stubailo Feb 17, 2017

Contributor

Or is it sending a package of "something" (including a GraphQL query, variables, etc) to something else, and then that something else is interacting with GraphQL?

Yes, the idea is that you can add subscriptions on top of the existing GraphQL execution libraries by adding a subscription manager or subscription gateway. And that thing's job is to re-execute the subscription query against the GraphQL server in response to the event.

I imagine implementation-wise there's not much actual change, right? As set up right now, GraphQL.js can execute a subscription query, but the actual semantics of the full request live outside.

I think that's about right - in terms of a library like GraphQL.js I think the one thing that could be added is a way to map the subscriptions fields to events. I think there would be a lot of value in putting that right next to the resolver code.

Oh, and one more thing - the resolver for a subscription field has one more parameter now, which is the "payload", so now that resolver takes in rootValue, args, context, and payload.

That could come in three different places:

  1. The payload could be passed as the root value, that would be consistent with looking at the subscription as a continuation of the mutation - so the mutation can push its result over the message system, and the subscription field picks up where it left off. But that could be confusing given that root values are used for other stuff as well.
  2. The payload is attached to context, but that feels like a bit of a hack to me especially since it means all fields will be able to access it.
  3. There's a new parameter that only subscriptions field resolvers get access to.

I think I'm in favor of (3), which requires a change to GraphQL.js to have the execution function take an extra subscriptionPayload parameter or similar.

Contributor

stubailo commented Feb 17, 2017

Or is it sending a package of "something" (including a GraphQL query, variables, etc) to something else, and then that something else is interacting with GraphQL?

Yes, the idea is that you can add subscriptions on top of the existing GraphQL execution libraries by adding a subscription manager or subscription gateway. And that thing's job is to re-execute the subscription query against the GraphQL server in response to the event.

I imagine implementation-wise there's not much actual change, right? As set up right now, GraphQL.js can execute a subscription query, but the actual semantics of the full request live outside.

I think that's about right - in terms of a library like GraphQL.js I think the one thing that could be added is a way to map the subscriptions fields to events. I think there would be a lot of value in putting that right next to the resolver code.

Oh, and one more thing - the resolver for a subscription field has one more parameter now, which is the "payload", so now that resolver takes in rootValue, args, context, and payload.

That could come in three different places:

  1. The payload could be passed as the root value, that would be consistent with looking at the subscription as a continuation of the mutation - so the mutation can push its result over the message system, and the subscription field picks up where it left off. But that could be confusing given that root values are used for other stuff as well.
  2. The payload is attached to context, but that feels like a bit of a hack to me especially since it means all fields will be able to access it.
  3. There's a new parameter that only subscriptions field resolvers get access to.

I think I'm in favor of (3), which requires a change to GraphQL.js to have the execution function take an extra subscriptionPayload parameter or similar.

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 17, 2017

It'd be great if running a subscription could return some description of the relevant event set as a first-order concern, though the current workarounds aren't too bad.

I'd prefer subscription payload come through as rootValue for consistency.

taion commented Feb 17, 2017

It'd be great if running a subscription could return some description of the relevant event set as a first-order concern, though the current workarounds aren't too bad.

I'd prefer subscription payload come through as rootValue for consistency.

@stubailo

This comment has been minimized.

Show comment
Hide comment
@stubailo

stubailo Feb 17, 2017

Contributor

I'd prefer subscription payload come through as rootValue for consistency.

That's what the graphql-subscriptions npm package does right now, and it's not too bad. But we don't really use rootValue for anything else since we basically put everything on context.

Contributor

stubailo commented Feb 17, 2017

I'd prefer subscription payload come through as rootValue for consistency.

That's what the graphql-subscriptions npm package does right now, and it's not too bad. But we don't really use rootValue for anything else since we basically put everything on context.

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 17, 2017

Yeah, I think that's the right thing, no? Now that context exists, rootValue is exactly just for injecting payload in that manner, I think.

taion commented Feb 17, 2017

Yeah, I think that's the right thing, no? Now that context exists, rootValue is exactly just for injecting payload in that manner, I think.

@smolinari

This comment has been minimized.

Show comment
Hide comment
@smolinari

smolinari Feb 18, 2017

I've read through everything once more and I think I might be missing something (probably general understanding). But, how does or should the subscription system fit in with Introspection?

Could someone also run through the subscription lifecycle with a use case? I am understanding two things here and I am not sure which one is right. Either the the event on the server triggers a requerying of a specific GraphQL query from the client, or the event on the server pushes the new results of a specific query automatically to the client. I'd like to understand which one it is through a use case. Or can both happen?

And a general comment. To me, the argumentation against a live query system referenced in the RFC is assuming that any standard GraphQL query should be subscribable as a live query. I'd suggest this is an incorrect assumption. Instead of any query being subscribable, there should only be specific (extra) queries that are subscribable. This should allow for a much finer control over what needs to be updated. It also simplifies the GraphQL subscription system enormously. You must admit, the event based system in this proposal is quite complex. The RFC even says it is the most complex way to go. To me, live queries are all GraphQL should be worried about. An events system, or a database that allows for live queries itself or whatever else userland can come up with to feed the live queries should be business domain specifics IMHO.

Scott

smolinari commented Feb 18, 2017

I've read through everything once more and I think I might be missing something (probably general understanding). But, how does or should the subscription system fit in with Introspection?

Could someone also run through the subscription lifecycle with a use case? I am understanding two things here and I am not sure which one is right. Either the the event on the server triggers a requerying of a specific GraphQL query from the client, or the event on the server pushes the new results of a specific query automatically to the client. I'd like to understand which one it is through a use case. Or can both happen?

And a general comment. To me, the argumentation against a live query system referenced in the RFC is assuming that any standard GraphQL query should be subscribable as a live query. I'd suggest this is an incorrect assumption. Instead of any query being subscribable, there should only be specific (extra) queries that are subscribable. This should allow for a much finer control over what needs to be updated. It also simplifies the GraphQL subscription system enormously. You must admit, the event based system in this proposal is quite complex. The RFC even says it is the most complex way to go. To me, live queries are all GraphQL should be worried about. An events system, or a database that allows for live queries itself or whatever else userland can come up with to feed the live queries should be business domain specifics IMHO.

Scott

@PaulLeCam

This comment has been minimized.

Show comment
Hide comment
@PaulLeCam

PaulLeCam Feb 20, 2017

Great to see this added to the spec!

We have been using relay-subscriptions for the last few months at Mainframe, and I wonder how much of this spec differs from relay-subscriptions’ implementation?
In our case, with the only exception of handling notifications, all our subscriptions are linked to queries, so they notably use the same fragments, as in relay-subscriptions’ example.

One issue we needed to address was the possibility to miss relevant events between the time a query is resolved and the subscription is created on the server, as the current flow using relay-subscriptions is client queries -> server responds -> client creates subscription using response payload -> server receives subscription request -> server publishes subscription events when receiving internal events.
Our current workaround is to have the server provide a “version” (opaque string for the client) when responding to any query, that the client sends along with the subscription requests related to the query, allowing the server to look back at the events log and publish the needed subscription events to the client so it can catch-up on any change.
We implemented this logic in our custom network layer, but considering it is affected by the relations between queries and subscriptions, I wonder if it is something that could be part of this spec?

PaulLeCam commented Feb 20, 2017

Great to see this added to the spec!

We have been using relay-subscriptions for the last few months at Mainframe, and I wonder how much of this spec differs from relay-subscriptions’ implementation?
In our case, with the only exception of handling notifications, all our subscriptions are linked to queries, so they notably use the same fragments, as in relay-subscriptions’ example.

One issue we needed to address was the possibility to miss relevant events between the time a query is resolved and the subscription is created on the server, as the current flow using relay-subscriptions is client queries -> server responds -> client creates subscription using response payload -> server receives subscription request -> server publishes subscription events when receiving internal events.
Our current workaround is to have the server provide a “version” (opaque string for the client) when responding to any query, that the client sends along with the subscription requests related to the query, allowing the server to look back at the events log and publish the needed subscription events to the client so it can catch-up on any change.
We implemented this logic in our custom network layer, but considering it is affected by the relations between queries and subscriptions, I wonder if it is something that could be part of this spec?

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 20, 2017

Again, I think that's the sort of thing that really depends. In many cases I think that's just not required, and would instead just be extra overhead.

taion commented Feb 20, 2017

Again, I think that's the sort of thing that really depends. In many cases I think that's just not required, and would instead just be extra overhead.

@rmosolgo

This comment has been minimized.

Show comment
Hide comment
@rmosolgo

rmosolgo Feb 20, 2017

That's interesting, it's actually a case where my misunderstanding of subscriptions would come in handy.

In that scenario, a subscription request also returned an immediate response, so you could get the current server state and make a subscription in a single operation.

However, I'm not sure how you'd sort it out in a case like this:

subscription getNewComment($postId: $ID!) {
  commentAdded(postId: $postId) {
    newComment { 
      author { name }
      message
    }
    post { 
      title 
      comments { message } 
    }
  }
}

In that case, the first response comes with newComment: null but post: { ... } is present, so you can initialize the UI with that state.

The problem is, on later requests, you'd still get post: { ... }, which is overfetching.

So, I don't think it's much of an answer, but I thought I'd share anyways 😆

rmosolgo commented Feb 20, 2017

That's interesting, it's actually a case where my misunderstanding of subscriptions would come in handy.

In that scenario, a subscription request also returned an immediate response, so you could get the current server state and make a subscription in a single operation.

However, I'm not sure how you'd sort it out in a case like this:

subscription getNewComment($postId: $ID!) {
  commentAdded(postId: $postId) {
    newComment { 
      author { name }
      message
    }
    post { 
      title 
      comments { message } 
    }
  }
}

In that case, the first response comes with newComment: null but post: { ... } is present, so you can initialize the UI with that state.

The problem is, on later requests, you'd still get post: { ... }, which is overfetching.

So, I don't think it's much of an answer, but I thought I'd share anyways 😆

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 20, 2017

Presumably at the implementation level, you can put something on context indicating whether this is the initial subscription request, then do whatever is appropriate in the resolver.

There's nothing that prevents an implementation from immediately firing an update corresponding to the current state – I just don't think it's required in the general case.

taion commented Feb 20, 2017

Presumably at the implementation level, you can put something on context indicating whether this is the initial subscription request, then do whatever is appropriate in the resolver.

There's nothing that prevents an implementation from immediately firing an update corresponding to the current state – I just don't think it's required in the general case.

@paralin

This comment has been minimized.

Show comment
Hide comment
@paralin

paralin Feb 21, 2017

I'm going to just drop a link to my Real-Time GraphQL Project here - http://github.com/rgraphql/magellan - where I define a protocol and proof of concept implementation for two-way communication between a GraphQL client and server, including real-time updates to results and queries.

Subscriptions seem like an inelegant solution to me. They are extremely limited, one-way, one-time subscribe, unable to update query parameters after the subscription has started, etc. As you're finding now in this thread, these limitations make using and implementing subscriptions a muddy deal.

If something's not simple, it's probably wrong. And subscriptions to me seem to have a lot of cludginess as a result of jerry-rigging them in to an existing one-way one-off GraphQL spec. With the assumption that we have a two-way communication path with the server, a lot of optimizations and improvements can be made. I've written a proof of concept of this at the rgraphql repo I linked above. A demo is also available here: http://rgraphql.org

Curious to hear your opinions on this.

paralin commented Feb 21, 2017

I'm going to just drop a link to my Real-Time GraphQL Project here - http://github.com/rgraphql/magellan - where I define a protocol and proof of concept implementation for two-way communication between a GraphQL client and server, including real-time updates to results and queries.

Subscriptions seem like an inelegant solution to me. They are extremely limited, one-way, one-time subscribe, unable to update query parameters after the subscription has started, etc. As you're finding now in this thread, these limitations make using and implementing subscriptions a muddy deal.

If something's not simple, it's probably wrong. And subscriptions to me seem to have a lot of cludginess as a result of jerry-rigging them in to an existing one-way one-off GraphQL spec. With the assumption that we have a two-way communication path with the server, a lot of optimizations and improvements can be made. I've written a proof of concept of this at the rgraphql repo I linked above. A demo is also available here: http://rgraphql.org

Curious to hear your opinions on this.

@dschafer

This comment has been minimized.

Show comment
Hide comment
@dschafer

dschafer Feb 21, 2017

Contributor

Liking the looks of the RFC so far!

One thing that's not in the RFC right now but that might be valuable: do we want to require that the sequence of payloads sent to the subscriber is determined only by the root subscription field (and its arguments), and that in particular the fieldset of that subscription field does not affect the sequence of payloads?


I think a concrete example probably demonstrates the value of this best; I imagine a developer on the client writing

subscription DeveloperSubscription {
  commentCreate(postId: 1234) {
    comment {
      text
      timeCreated
      author {
        ...CommentAuthorFragment
      }
    }
  }
}

This is a pretty basic subscription; every time there's a new comment, push a payload that includes the text of the comment, time created, and the author information (where that's delegated to another fragment, one that presumably defined by CommentAuthorView or something).

Now I imagine using a client-side GraphQL framework to issue that subscription; if that framework has a normalized cache, it might want to augment the subscription to fetch IDs for some of the objects so that it can update the cache. So rather than issuing DeveloperSubscription, it would instead issue:

subscription FrameworkAugmentedSubscription {
  commentCreate(postId: 1234) {
    comment {
      id # Added by framework
      text
      timeCreated
      author {
        id # Added by framework
        ...CommentAuthorFragment
      }
    }
  }
}

Meanwhile, our developer wants to debug this, and opens up Graphiql. The whole author fragment might be big and unwieldy and all the developer wants to do is verify that the subscription is working as expected, so the developer might issue the following in Graphiql:

subscription DebuggingSubscription {
  commentCreate(postId: 1234) {
    comment {
      text
    }
  }
}

Of course, the payloads received by these three subscriptions will be different between the three, since the field sets are different. But because the root subscription field on all three was the same, commentCreate(postId: 1234), it feels like we should be able to guarantee the sequence is the same: if one subscription receives a payload, the other two should as well. It seems like without the guarantee, we might have three potential issues.

  • A client-side framework can't safely modify the field set without risking changing the functionality of the subscription.
  • A debugging developer can't reduce the size of the field set in Graphiql while guaranteeing that they are still seeing the same sequence as the client they are debugging.
  • We kinda break encapsulation: in the DeveloperSubscription example, the developer's intention is to subscribe to all comments created... but if the field set can alter the sequence, then changes to CommentAuthorFragment can alter the sequence. This feels kind of weird, the developer only spread in CommentAuthorFragment to ensure it fetched the right fields in the payload to render the comment author; the idea that a change to CommentAuthorFragment could alter the functionality of DeveloperSubscription feels odd.

Thoughts?

Contributor

dschafer commented Feb 21, 2017

Liking the looks of the RFC so far!

One thing that's not in the RFC right now but that might be valuable: do we want to require that the sequence of payloads sent to the subscriber is determined only by the root subscription field (and its arguments), and that in particular the fieldset of that subscription field does not affect the sequence of payloads?


I think a concrete example probably demonstrates the value of this best; I imagine a developer on the client writing

subscription DeveloperSubscription {
  commentCreate(postId: 1234) {
    comment {
      text
      timeCreated
      author {
        ...CommentAuthorFragment
      }
    }
  }
}

This is a pretty basic subscription; every time there's a new comment, push a payload that includes the text of the comment, time created, and the author information (where that's delegated to another fragment, one that presumably defined by CommentAuthorView or something).

Now I imagine using a client-side GraphQL framework to issue that subscription; if that framework has a normalized cache, it might want to augment the subscription to fetch IDs for some of the objects so that it can update the cache. So rather than issuing DeveloperSubscription, it would instead issue:

subscription FrameworkAugmentedSubscription {
  commentCreate(postId: 1234) {
    comment {
      id # Added by framework
      text
      timeCreated
      author {
        id # Added by framework
        ...CommentAuthorFragment
      }
    }
  }
}

Meanwhile, our developer wants to debug this, and opens up Graphiql. The whole author fragment might be big and unwieldy and all the developer wants to do is verify that the subscription is working as expected, so the developer might issue the following in Graphiql:

subscription DebuggingSubscription {
  commentCreate(postId: 1234) {
    comment {
      text
    }
  }
}

Of course, the payloads received by these three subscriptions will be different between the three, since the field sets are different. But because the root subscription field on all three was the same, commentCreate(postId: 1234), it feels like we should be able to guarantee the sequence is the same: if one subscription receives a payload, the other two should as well. It seems like without the guarantee, we might have three potential issues.

  • A client-side framework can't safely modify the field set without risking changing the functionality of the subscription.
  • A debugging developer can't reduce the size of the field set in Graphiql while guaranteeing that they are still seeing the same sequence as the client they are debugging.
  • We kinda break encapsulation: in the DeveloperSubscription example, the developer's intention is to subscribe to all comments created... but if the field set can alter the sequence, then changes to CommentAuthorFragment can alter the sequence. This feels kind of weird, the developer only spread in CommentAuthorFragment to ensure it fetched the right fields in the payload to render the comment author; the idea that a change to CommentAuthorFragment could alter the functionality of DeveloperSubscription feels odd.

Thoughts?

@rmosolgo

This comment has been minimized.

Show comment
Hide comment
@rmosolgo

rmosolgo Feb 21, 2017

@dschafer The spec contains special rules for root fields on a Mutation type. It sounds like you're suggesting that the root fields of Subscription type have a special property also: only the root fields determine how a client is subscribed, so if two queries have the same root fields, they result in the same subscribed state (regardless of any child selections).

Is that correct? If so, big 👍 from me. I somehow had that assumption, but I didn't notice it was missing from this RFC!

rmosolgo commented Feb 21, 2017

@dschafer The spec contains special rules for root fields on a Mutation type. It sounds like you're suggesting that the root fields of Subscription type have a special property also: only the root fields determine how a client is subscribed, so if two queries have the same root fields, they result in the same subscribed state (regardless of any child selections).

Is that correct? If so, big 👍 from me. I somehow had that assumption, but I didn't notice it was missing from this RFC!

@smolinari

This comment has been minimized.

Show comment
Hide comment
@smolinari

smolinari Feb 27, 2017

@rzachariah - Correct me if I am wrong, but your example is one where a polling type push of data (the event to GraphQL) is going to be the result of the aggregation of top tweets. In other words, you wouldn't want to push data for each tweet, but rather only every minute or 10 seconds or whatever is smart for your users and your system. It is a great example of how diverse the events could be, which trigger publishing data to subscribers.

On a side note: The more I read and learn, the more I see that the pub-sub system, which is put forward in this RFC, is actually what I consider a live query system. So, I apologize for my ramblings.

I've also noticed that "live query" has been defined in different ways for different technologies, from databases to jQuery to Meteor. So, seeing as there is no one clear standard for what a live query is, I'd like to also suggest we call this system "GraphQL Live Query", simply because that name is a lot sexier than "pub-sub" or "GraphQL Subscriptions". 😄

Or is there a clear definition of what a live query is? Let's forget what was put forward as a live query in this RFC, as it also isn't a standard either. 😉 How about it?

Scott

smolinari commented Feb 27, 2017

@rzachariah - Correct me if I am wrong, but your example is one where a polling type push of data (the event to GraphQL) is going to be the result of the aggregation of top tweets. In other words, you wouldn't want to push data for each tweet, but rather only every minute or 10 seconds or whatever is smart for your users and your system. It is a great example of how diverse the events could be, which trigger publishing data to subscribers.

On a side note: The more I read and learn, the more I see that the pub-sub system, which is put forward in this RFC, is actually what I consider a live query system. So, I apologize for my ramblings.

I've also noticed that "live query" has been defined in different ways for different technologies, from databases to jQuery to Meteor. So, seeing as there is no one clear standard for what a live query is, I'd like to also suggest we call this system "GraphQL Live Query", simply because that name is a lot sexier than "pub-sub" or "GraphQL Subscriptions". 😄

Or is there a clear definition of what a live query is? Let's forget what was put forward as a live query in this RFC, as it also isn't a standard either. 😉 How about it?

Scott

@rzachariah

This comment has been minimized.

Show comment
Hide comment
@rzachariah

rzachariah Feb 27, 2017

Hi @smolinari. Yes, in my example, the top trends event stream is a streaming aggregation of the tweet stream. The throughput (events/second) of this derived event stream would be much lower than the raw event stream that produced it. There's some natural filtering as a result of the top-x operation. In addition, yes, it would be reasonable to introduce additional delays/batching to reduce the computational load. A top 10 trends dashboard that's live as of a few seconds ago, or even a minute ago, seems reasonable.

I was also trying to suggest a more efficient alternative to simply iterating over the last 6 hours of tweet data every time you're interested in updating the trends dashboard. I proposed a storm topology as an approach that maintains intermediate state about the computation (sliding window counts by topic, current rankings), so there's very little work to do whenever it's time to process a new tweet. It's an event processing pipeline made up of a chain of subscriptions. An Rx query would be another example of an event processing pipeline made up of a chain of subscriptions, and it's possible to write one for this example (though the Rx version would not be as scalable).

One could think of a storm topology or an Rx query as a live query. It's a query that, once deployed or subscribed to, is always running and automatically pushes updates in reaction to new input events.

rzachariah commented Feb 27, 2017

Hi @smolinari. Yes, in my example, the top trends event stream is a streaming aggregation of the tweet stream. The throughput (events/second) of this derived event stream would be much lower than the raw event stream that produced it. There's some natural filtering as a result of the top-x operation. In addition, yes, it would be reasonable to introduce additional delays/batching to reduce the computational load. A top 10 trends dashboard that's live as of a few seconds ago, or even a minute ago, seems reasonable.

I was also trying to suggest a more efficient alternative to simply iterating over the last 6 hours of tweet data every time you're interested in updating the trends dashboard. I proposed a storm topology as an approach that maintains intermediate state about the computation (sliding window counts by topic, current rankings), so there's very little work to do whenever it's time to process a new tweet. It's an event processing pipeline made up of a chain of subscriptions. An Rx query would be another example of an event processing pipeline made up of a chain of subscriptions, and it's possible to write one for this example (though the Rx version would not be as scalable).

One could think of a storm topology or an Rx query as a live query. It's a query that, once deployed or subscribed to, is always running and automatically pushes updates in reaction to new input events.

@jamesgorman2

This comment has been minimized.

Show comment
Hide comment
@jamesgorman2

jamesgorman2 Feb 27, 2017

Some thoughts on this I've had reading the RFC and this thread (in no particular order):

  • the case for subscriptions inside graphql needs to be made more strongly. 'Ah well, you can just implement that in your transport layer' seems to have come up a few times.
  • in img_01, why does the subscription layer talk directly to graphql? This feels like a break in the abstraction.
  • there feels like there is a gap between Data-transform pipelines and Live Queries for entity-state updates. The main problem with live queries is not business logic decisions[1] but computation and dependency complexity. If I have an n depth query I am subscribed and an object at depth n changes, propagating this back down to the root subscriptions is difficult (not impossible, but I'd rather not have to think about how to get it to wire up!). Comments by @leebyron, @rmosolgo and @stubailo about restricting subscription semantics to the root parameters all speak to this.
    • I think restricting subscription semantics to the root parameters is generally a good restriction to make, but it needs to be made clearly and with reasons given if it is to be more than a recommendation
    • talking about live queries is unclear (and has already caused some confusion). This seems to be a DB-land phrase originally, but it does appear to be causing confusion. It would be better to frame everything in terms of the construction of graphql queries and their result sets.
    • distinguishing between events (state mutation events), actions/commands (not in here yet, but closely related in the eventing world) and whole state objects is immaterial given the above, particularly since they are all types of event and the distinction is unenforceable.
  • in img_01 most the green subscription layer seems unnecessary. A graphql subscription would work without any bi-directional traffic from downstream. Leaving this in may reduce the clarity of the message (it could perhaps go in an addendum to describe a more complex system).
  • there is definitely room for best practice stuff around designing events, but this should be demarcated as best practice since it's stuff that is contingently true.

[1] here I disagree with Dan Schafer and Laney Kuenzel's assessment of Live Queries in Relay (though not their decision). I think their domain problem (ie, when do I update?) is technically solved (eg using some sort of buffer/aggregation or debouncing, ignoring the computational cost aspect) but requires a product decision as to what should updates be seen when. Their second point about computational complexity speaks only to that specific use case, not all use cases.

jamesgorman2 commented Feb 27, 2017

Some thoughts on this I've had reading the RFC and this thread (in no particular order):

  • the case for subscriptions inside graphql needs to be made more strongly. 'Ah well, you can just implement that in your transport layer' seems to have come up a few times.
  • in img_01, why does the subscription layer talk directly to graphql? This feels like a break in the abstraction.
  • there feels like there is a gap between Data-transform pipelines and Live Queries for entity-state updates. The main problem with live queries is not business logic decisions[1] but computation and dependency complexity. If I have an n depth query I am subscribed and an object at depth n changes, propagating this back down to the root subscriptions is difficult (not impossible, but I'd rather not have to think about how to get it to wire up!). Comments by @leebyron, @rmosolgo and @stubailo about restricting subscription semantics to the root parameters all speak to this.
    • I think restricting subscription semantics to the root parameters is generally a good restriction to make, but it needs to be made clearly and with reasons given if it is to be more than a recommendation
    • talking about live queries is unclear (and has already caused some confusion). This seems to be a DB-land phrase originally, but it does appear to be causing confusion. It would be better to frame everything in terms of the construction of graphql queries and their result sets.
    • distinguishing between events (state mutation events), actions/commands (not in here yet, but closely related in the eventing world) and whole state objects is immaterial given the above, particularly since they are all types of event and the distinction is unenforceable.
  • in img_01 most the green subscription layer seems unnecessary. A graphql subscription would work without any bi-directional traffic from downstream. Leaving this in may reduce the clarity of the message (it could perhaps go in an addendum to describe a more complex system).
  • there is definitely room for best practice stuff around designing events, but this should be demarcated as best practice since it's stuff that is contingently true.

[1] here I disagree with Dan Schafer and Laney Kuenzel's assessment of Live Queries in Relay (though not their decision). I think their domain problem (ie, when do I update?) is technically solved (eg using some sort of buffer/aggregation or debouncing, ignoring the computational cost aspect) but requires a product decision as to what should updates be seen when. Their second point about computational complexity speaks only to that specific use case, not all use cases.

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Feb 27, 2017

FWIW, I'd strongly recommend taking a look at graphql-subscriptions and graphql-relay-subscription (with relay-subscriptions), if you haven't already.

I think there already are implementations that hew quite closely to what's in the proposal. Some of these are even already live.

Those systems, their relevant API warts, and the current GraphQL reference implementation (which has already moved ahead of the spec here) are in some sense existence proofs for what makes sense engineering-wise to split between core and transport layers.

taion commented Feb 27, 2017

FWIW, I'd strongly recommend taking a look at graphql-subscriptions and graphql-relay-subscription (with relay-subscriptions), if you haven't already.

I think there already are implementations that hew quite closely to what's in the proposal. Some of these are even already live.

Those systems, their relevant API warts, and the current GraphQL reference implementation (which has already moved ahead of the spec here) are in some sense existence proofs for what makes sense engineering-wise to split between core and transport layers.

@jamesgorman2

This comment has been minimized.

Show comment
Hide comment
@jamesgorman2

jamesgorman2 Feb 27, 2017

jamesgorman2 commented Feb 27, 2017

@smolinari

This comment has been minimized.

Show comment
Hide comment
@smolinari

smolinari Feb 28, 2017

@rzachariah

It's a query that, once deployed or subscribed to, is always running and automatically pushes updates in reaction to new input events.

That sounds like exactly what the one-sentence-definition of GraphQL Subscriptions or ehem.... Live Query 😄 should be. 😉

Scott

smolinari commented Feb 28, 2017

@rzachariah

It's a query that, once deployed or subscribed to, is always running and automatically pushes updates in reaction to new input events.

That sounds like exactly what the one-sentence-definition of GraphQL Subscriptions or ehem.... Live Query 😄 should be. 😉

Scott

@paralin

This comment has been minimized.

Show comment
Hide comment
@paralin

paralin Feb 28, 2017

@smolinari I hate to keep mentioning it, but I have actually proven in rgraphql that "live" Graphql queries with arbitrary data sources (no DB related logic) and mixes of static and dynamic data is easily possible without changing the current spec at all. What these guys are working on is a extension to the spec that works around the cludge of typical one-way data sources like Pub/Sub or long polling on requests. Otherwise there's no advantage to the flat limited subscription model when compared to a streaming system.

paralin commented Feb 28, 2017

@smolinari I hate to keep mentioning it, but I have actually proven in rgraphql that "live" Graphql queries with arbitrary data sources (no DB related logic) and mixes of static and dynamic data is easily possible without changing the current spec at all. What these guys are working on is a extension to the spec that works around the cludge of typical one-way data sources like Pub/Sub or long polling on requests. Otherwise there's no advantage to the flat limited subscription model when compared to a streaming system.

@smolinari

This comment has been minimized.

Show comment
Hide comment
@smolinari

smolinari Feb 28, 2017

Ok. @paralin Still, I am a bit confused. Are you making that point for me, because I am misunderstanding something or are you making that point to everyone else, to say all this isn't really necessary to make a live query system within the spec? Or maybe even both? LOL! 😄

Scott

smolinari commented Feb 28, 2017

Ok. @paralin Still, I am a bit confused. Are you making that point for me, because I am misunderstanding something or are you making that point to everyone else, to say all this isn't really necessary to make a live query system within the spec? Or maybe even both? LOL! 😄

Scott

@paralin

This comment has been minimized.

Show comment
Hide comment
@paralin

paralin Feb 28, 2017

The spec already covers everything for live queries, other than I suppose directives to control the execution. But that's more of a server specific thing anyway.

What's the difference of a live query to the spec? You're still specifying what data is required in the same syntax.

paralin commented Feb 28, 2017

The spec already covers everything for live queries, other than I suppose directives to control the execution. But that's more of a server specific thing anyway.

What's the difference of a live query to the spec? You're still specifying what data is required in the same syntax.

@robzhu

This comment has been minimized.

Show comment
Hide comment
@robzhu

robzhu Feb 28, 2017

Contributor

Hey all, amazing feedback and discussion so far, but it’s clear the github PR single-comment-thread feedback format is not suited for hosting multiple conversations at once.

I’m going to go through the feedback and organize everything into open and closed topics. Closed topics are either obvious or have clear consensus and will be merged into the proposal in the next edit. Open topics are still being discussed (I will try to tag everyone who’s been involved/interested on each topic). Once I’ve organized this list, I’ll need everyone’s help:

  • Make sure the list is complete and the categorization of open/closed topics is accurate
  • Make sure your name is tagged on the topics you care about.

After that, I’ll merge this PR (clearly marked as "WIP") and open issues for each of the open topics so that we can have a more focused conversation. As each open topic is resolved, we will make the appropriate edits as part of a new PR. My main goals are to make sure all the great conversations here are captured without competing for attention and that we can make incremental progress on getting this into the Spec.

Contributor

robzhu commented Feb 28, 2017

Hey all, amazing feedback and discussion so far, but it’s clear the github PR single-comment-thread feedback format is not suited for hosting multiple conversations at once.

I’m going to go through the feedback and organize everything into open and closed topics. Closed topics are either obvious or have clear consensus and will be merged into the proposal in the next edit. Open topics are still being discussed (I will try to tag everyone who’s been involved/interested on each topic). Once I’ve organized this list, I’ll need everyone’s help:

  • Make sure the list is complete and the categorization of open/closed topics is accurate
  • Make sure your name is tagged on the topics you care about.

After that, I’ll merge this PR (clearly marked as "WIP") and open issues for each of the open topics so that we can have a more focused conversation. As each open topic is resolved, we will make the appropriate edits as part of a new PR. My main goals are to make sure all the great conversations here are captured without competing for attention and that we can make incremental progress on getting this into the Spec.

@smolinari

This comment has been minimized.

Show comment
Hide comment
@smolinari

smolinari Mar 1, 2017

@paralin

What's the difference of a live query to the spec? You're still specifying what data is required in the same syntax.

Somehow a query needs to be designated as "live" by the client. Or, as you said, and I think we are on the same wavelength on this, the queries that can be called as "live" need to be defined on the server in some way and be obvious/ findable through introspection.

Scott

smolinari commented Mar 1, 2017

@paralin

What's the difference of a live query to the spec? You're still specifying what data is required in the same syntax.

Somehow a query needs to be designated as "live" by the client. Or, as you said, and I think we are on the same wavelength on this, the queries that can be called as "live" need to be defined on the server in some way and be obvious/ findable through introspection.

Scott

@robzhu

This comment has been minimized.

Show comment
Hide comment
@robzhu

robzhu Mar 1, 2017

Contributor

Here's my attempt to organize this thread into topics:

Closed

  • Add clearer notice that the RFC is a work-in-progress.
  • Add backward arrow from GraphQL to Subscriptions box in diagram (@rmosolgo)
  • Make "subscription active" part of the lifecycle optional and recommended for client-side error-handling (@taion, @stubailo)

Open

Please let me know if I've missed your topic or if you would like to be added/removed from anything. I'll leave this post here for a bit so everyone tagged has a chance to review. After that, I'm going to merge this PR and file issues for all open topics.

Contributor

robzhu commented Mar 1, 2017

Here's my attempt to organize this thread into topics:

Closed

  • Add clearer notice that the RFC is a work-in-progress.
  • Add backward arrow from GraphQL to Subscriptions box in diagram (@rmosolgo)
  • Make "subscription active" part of the lifecycle optional and recommended for client-side error-handling (@taion, @stubailo)

Open

Please let me know if I've missed your topic or if you would like to be added/removed from anything. I'll leave this post here for a bit so everyone tagged has a chance to review. After that, I'm going to merge this PR and file issues for all open topics.

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Mar 1, 2017

Do we need a "bi-directional" channel between client and server?

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.

Where should subscription state be stored? (inside GraphQL-js or as a separate component?)

I'd prefer to have this state be stored outside of GraphQL.js. I want to e.g. manage my own Redis subscriptions backing my GraphQL subscriptions, and I feel like the control logic is more straightforward if I have something I can call when my subscription fires, rather than passing e.g. an observable into GraphQL.js.

How should we pass event payloads/data into the subscription resolvers?

This is a framework-level implementation question. I'd prefer GraphQL.js use the existing rootValue field here.

The "event stream" should be derived purely from the subscription root field (and ignore selection set).

I agree with this. It's the "natural" way to write the spec.

Fetching initial state when subscribing.

I think this is an application-level thing. "State" is not a concept that is inherent to GraphQL. Subscribing to e.g. streams of newly created items also doesn't necessarily admit an easy representation of current state. Imagine subscription updates that don't reflect idempotent operations – in such cases there isn't really a coherent initial state.

Re-define Live Queries? Can Live Queries make Subscriptions unnecessary?

@stubailo's point is exactly right here. Live queries should be thought of separately from event-based subscriptions. They can be used to solve similar problems. However, scaling event-stream-based subscription systems is relatively well-understood; the same does not apply for live queries, and for anyone looking to ship something scalable now, live queries aren't really an option.

Intermediate Subscription event processing

This is an application-level detail. If someone wants to subscribe to e.g. a "top X" stream but not re-process that stream, then there's nothing at the framework level that prevents e.g. the "trending tweets" subscription from using different events than the "new tweet" subscription.

taion commented Mar 1, 2017

Do we need a "bi-directional" channel between client and server?

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.

Where should subscription state be stored? (inside GraphQL-js or as a separate component?)

I'd prefer to have this state be stored outside of GraphQL.js. I want to e.g. manage my own Redis subscriptions backing my GraphQL subscriptions, and I feel like the control logic is more straightforward if I have something I can call when my subscription fires, rather than passing e.g. an observable into GraphQL.js.

How should we pass event payloads/data into the subscription resolvers?

This is a framework-level implementation question. I'd prefer GraphQL.js use the existing rootValue field here.

The "event stream" should be derived purely from the subscription root field (and ignore selection set).

I agree with this. It's the "natural" way to write the spec.

Fetching initial state when subscribing.

I think this is an application-level thing. "State" is not a concept that is inherent to GraphQL. Subscribing to e.g. streams of newly created items also doesn't necessarily admit an easy representation of current state. Imagine subscription updates that don't reflect idempotent operations – in such cases there isn't really a coherent initial state.

Re-define Live Queries? Can Live Queries make Subscriptions unnecessary?

@stubailo's point is exactly right here. Live queries should be thought of separately from event-based subscriptions. They can be used to solve similar problems. However, scaling event-stream-based subscription systems is relatively well-understood; the same does not apply for live queries, and for anyone looking to ship something scalable now, live queries aren't really an option.

Intermediate Subscription event processing

This is an application-level detail. If someone wants to subscribe to e.g. a "top X" stream but not re-process that stream, then there's nothing at the framework level that prevents e.g. the "trending tweets" subscription from using different events than the "new tweet" subscription.

@paralin

This comment has been minimized.

Show comment
Hide comment
@paralin

paralin Mar 2, 2017

@taion Live queries certainly could be scalable, especially with GraphQL's emphasis on separating logic for each field. I'm looking at implementing a scheduler for Magellan that could do that.

But, you're right. Subscriptions are a different topic from live queries. And IMO, live queries don't need a change to the spec to be implemented. Directives are server-specific with introspection, and that should be all you need.

paralin commented Mar 2, 2017

@taion Live queries certainly could be scalable, especially with GraphQL's emphasis on separating logic for each field. I'm looking at implementing a scheduler for Magellan that could do that.

But, you're right. Subscriptions are a different topic from live queries. And IMO, live queries don't need a change to the spec to be implemented. Directives are server-specific with introspection, and that should be all you need.

@taion

This comment has been minimized.

Show comment
Hide comment
@taion

taion Mar 3, 2017

Just to clarify, when I say "live queries", I mean something like the @live directive mentioned in talks, where the interface presented to the client is one where the client can request a particular query be live, rather than one where the client explicitly requests specific streams.

For an example of a subscription-based API, see https://github.com/edvinerikson/relay-subscriptions/blob/v1.0.0/examples/todo/js/components/Todo.js#L124-L148.

If this were using live queries, then perhaps it would just look like:

fragment on Todo @live {
  id
  complete
  text
  # ...
}

However, if you're doing various things such as using microservices and splitting up GraphQL objects across multiple backend services, &c., how to efficiently implement live queries can be much less than obvious, while event-based systems are much more straightforward.

taion commented Mar 3, 2017

Just to clarify, when I say "live queries", I mean something like the @live directive mentioned in talks, where the interface presented to the client is one where the client can request a particular query be live, rather than one where the client explicitly requests specific streams.

For an example of a subscription-based API, see https://github.com/edvinerikson/relay-subscriptions/blob/v1.0.0/examples/todo/js/components/Todo.js#L124-L148.

If this were using live queries, then perhaps it would just look like:

fragment on Todo @live {
  id
  complete
  text
  # ...
}

However, if you're doing various things such as using microservices and splitting up GraphQL objects across multiple backend services, &c., how to efficiently implement live queries can be much less than obvious, while event-based systems are much more straightforward.

@leebyron

This comment has been minimized.

Show comment
Hide comment
@leebyron

leebyron Mar 7, 2017

Collaborator

Since there's excitement and support for the direction of this RFC, I'm going to commit it as an official tracked RFC. @robzhu just opened up issues for all of the various topics still under discussion - so this is still very much an open design!

Collaborator

leebyron commented Mar 7, 2017

Since there's excitement and support for the direction of this RFC, I'm going to commit it as an official tracked RFC. @robzhu just opened up issues for all of the various topics still under discussion - so this is still very much an open design!

@leebyron leebyron merged commit 0681a50 into facebook:master Mar 7, 2017

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
@stubailo

This comment has been minimized.

Show comment
Hide comment
@stubailo

stubailo Mar 7, 2017

Contributor

So excited!

Contributor

stubailo commented Mar 7, 2017

So excited!

@smolinari

This comment has been minimized.

Show comment
Hide comment
@smolinari

smolinari Mar 7, 2017

Me too! 👍

Scott

smolinari commented Mar 7, 2017

Me too! 👍

Scott

@thetwosents

This comment has been minimized.

Show comment
Hide comment
@thetwosents

thetwosents Mar 12, 2017

AHHHHHHHHHHHHH!!!!!!!

thetwosents commented Mar 12, 2017

AHHHHHHHHHHHHH!!!!!!!

@rmosolgo rmosolgo referenced this pull request Mar 17, 2017

Closed

Subscriptions #613

@flagello flagello referenced this pull request Apr 9, 2017

Open

Product Roadmap #1

56 of 65 tasks complete

@wincent wincent referenced this pull request May 5, 2017

Closed

How to do live-reload #296

IvanGoncharov added a commit to APIs-guru/graphql that referenced this pull request Jun 17, 2017

[RFC] GraphQL Subscriptions (#267)
* RFC: GraphQL Subscriptions

* Fix cutoff content from last checkin

* Fix diagram spacing

* Fix diagram spacing for real

* Minor wording fixes and higher-res images

* More wording clarity fixes

* Make overpushing/underpush wording consistent

@babakness babakness referenced this pull request Aug 11, 2017

Closed

Subscriptions example #45

@acjay acjay referenced this pull request Nov 22, 2017

Open

Pre-RFC Live queries #386

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment