-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Spec: Formal Subscriptions Definition #305
Conversation
spec/Section 5 -- Validation.md
Outdated
body | ||
sender | ||
} | ||
# a second root field will cause this operation to fail validation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everywhere else in this file we rely on naming rather than comments to illustrate counterexamples. Perhaps we should do the same for consistency here. (ie. call the field disallowedSecondRootField
)?
spec/Section 5 -- Validation.md
Outdated
sender | ||
} | ||
# this also applies to introspection fields | ||
__typename |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear what we would name this one if we wanted to get rid of the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a description above.
@@ -143,6 +143,84 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): | |||
selection set. | |||
* Return an unordered map containing {data} and {errors}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Putting a comment here because silly GitHub won't let me comment on the lines above. Up on line 106-107 it says:
If mutations are supported, it must also provide a mutation root object type.
That should probably say:
If mutations or subscriptions are supported, it must also provide a mutation and subscription root object type, respectively.
Or words to that effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also noticed that a step should be added to {ExecuteRequest}
above to handle subscription requests.
spec/Section 6 -- Execution.md
Outdated
phases. Between the the subscribe and unsubscribe phases, the subscription is | ||
considered to be in the "active" state. | ||
|
||
### Subscribe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably add new subheadings to this section:
## Executing Operations
...
### Executing Queries
...
### Executing Mutations
...
### Executing Subscriptions
...
#### Subscribe
(etc)
spec/Section 6 -- Execution.md
Outdated
* **must** support cancellation of the subscription (aka unsubscribe). | ||
* **must** support a way to identify the subscriber/subscription pair (for | ||
example, a GUID/callback table or closure over the callback). | ||
* **may** include an initial response associated with executing the selection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capitalize "m" in "must"/"may" here for consistency with the other lists (that all seem to start with capitals)?
spec/Section 6 -- Execution.md
Outdated
|
||
* **must** support observation of the associated publish stream (for example, | ||
via iteration, callbacks, or reactive semantics). | ||
* **must** support cancellation of the subscription (aka unsubscribe). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"aka" is a bit informal: I'd suggest, "that is," instead.
spec/Section 6 -- Execution.md
Outdated
the following capabilities: | ||
|
||
* **must** support observation of the associated publish stream (for example, | ||
via iteration, callbacks, or reactive semantics). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what "iteration" means in the context of observation: do you specifically mean async iteration? (and if so, any concern that that is allowing JS-specific terminology to leak into the spec?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was referring to the general iterator pattern (async iterators also count). For example, Unity handles async operations via iterators/generators, so I don't think it's JS-specific.
spec/Section 6 -- Execution.md
Outdated
* **may** include an initial response associated with executing the selection | ||
set defined on the subscription operation. | ||
|
||
Subscribe(subscription, schema, variableValues, initialValue): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
initialValue
is not referenced anywhere in this section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch, this is a good place to talk about returning an initial response.
spec/Section 6 -- Execution.md
Outdated
|
||
Publish(subscription, schema, variableValues, payload, publishStream) | ||
|
||
* For each {eventStream} as {event} and {payload}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does payload
come from? On line 169 above it says that eventStream
is "the result" of running MapSubscriptionEvents
, but the "result" isn't defined anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rewrote this section to make it more clear.
spec/Section 6 -- Execution.md
Outdated
*normally* (allowing parallelization). | ||
* Let {errors} be any *field errors* produced while executing the | ||
selection set. | ||
* If {errors} is not empty, optionally terminate the subscription. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we define what termination means?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed "Unsubscribe"
spec/Section 6 -- Execution.md
Outdated
|
||
Unsubscribe() | ||
|
||
* Let {publishStream} be a mapping of {null} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reworded this to "Terminate {publishStream}" with some examples.
spec/Section 6 -- Execution.md
Outdated
* Subscriber/client channel | ||
* Subscription document | ||
* Variables | ||
* Execution context (for example, current logged in user, locale, etc.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/logged in/logged-in/
spec/Section 6 -- Execution.md
Outdated
|
||
Rather than mixing stateless (queries and mutations) and stateful | ||
(subscriptions), we recommend keeping the GraphQL server stateless and | ||
delegating all state persistence these sub-systems. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these sub-systems → to these sub-systems
|
||
**Explanatory Text** | ||
|
||
Subscription operations must have exactly one root field. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robzhu what is the reason for that? why can't i subscribe to more then one subscription with one call?
so far i thought this is implementation limitation..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with Rob's text that starting with just one root field is reasonable. A later version of the spec could support multiple fields, but that requires figuring out a lot of edge cases around errors, when subfields are re-executed, and more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also would like to know the reason for this rule. I find that it is quite hard limitation, and in a way it goes against GraphQL principles where clients have the power to decide what it wants to get.
that requires figuring out a lot of edge cases around errors
I think it would be helpful to list and discuss these edge cases. From my experience, there are definitely things to consider when merging different event streams from different GraphQL subscription fields, but I find it quite manageable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I listed most of the points I found as I was implementing it in sangria here:
I think that it boils down to points 3.i
, 5
and 6
. My take on it is here: #282 (comment)
IMHO, if we need to make a trade off, I would rather disallow not-null root subscription field types than allow only a single subscription field in a query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stubailo if one wants to experiment that's fine, but i don't think it should be in the official spec unless there is a reason (which is not implementation) behind it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stubailo thanks a lot for describing in on an example!
So you're sending two subscription fields in the request, but you're never going to get that entire response.
I don't see it as a problem but rather as a natural behavior. This is inherent property of event-based interaction. This is also the reason why I suggested to disallow not-null root subscription field types.
you can now only unsubscribe to the whole thing at once (maybe a benefit)
I also don't see it as a issue either. Can you describe in more detail why this behavior can be disadvantageous? (considering that you still can make 2 separate and isolated subscription queries if it suits better for the use-case at hand)
it's not clear what to do when one of them has a fatal error - do both get unsubscribed?
I feel that either behavior is fine as long as it is defined in the spec. Though in this case I would suggest draw inspiration from streaming libraries: if 2 event streams are joined/merged together in a single result stream, then an error in either of these will also case the result stream to fail. If one of event steams naturally completes (because of the exhaustion), then the result steam still continue to emit events until all of the source streams are exhausted. I think this behavior is quite intuitive and widespread.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@OlegIlyenko @DxCx To give a little bit of context on where this came from, we discovered early on that it was better to use subscriptions for modeling granular events. For example, consider the three main subscriptions that operate on a facebook post: live comments, live likes, and typing indicators. These are individual subscriptions as opposed to a single "postLiveUpdate" subscription. Keeping these subscriptions granular on the client made natural sense. @laneyk and @dschafer may be able to add more perspective here.
Thinking this through, if we include multiple root fields like so:
subscription sub(...) {
liveLike (...) {...}
likeComment (...) {...}
}
So far, everyone seems to assume this subscription should publish data when either "live like" or "live comment" publishes. Is that clearly the intent of this query? What if there were a desire to trigger the publish only when both root fields have a publish payload available? How would we describe that? By limiting the selection to a single root field, we sidestep all that.
I also don't think the single-root-field-rule introduces any practical limitations to the client. In fact, it results in simpler client-side code, like so:
likeSubscription.subscribe(payload => updateLikeState(payload));
For subscriptions containing more than one root field, if we assume the "or" behavior, as @stubailo points out, you'll never have more than one event trigger at a time, so the code would end up looking like:
genericSubscription.subscribe(payload => {
if (payload.subscriptionA) { updateA(payload.SubscriptionA); }
else if (payload.subscriptionB) { updateB(payload.SubscriptionB); }
// etc.
});
Can someone help me understand a compelling use case that is served by multi-root subscription operations that would not be equally served by separate individual subscriptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to add a meta-point to this conversation:
In my view, there's nothing stopping us from working through these details and figuring out the edge and corner cases of allowing multiple subscription fields in a single request, however the choice we do have is to address those concerns now, or allow for more time to do so. In previous conversations @robzhu has had over the last few months about subscriptions, he has convinced many that this is far more complicated than we originally thought and may not have clear answers. This limitation is added mostly in a desire to expedite the addition of subscriptions to the spec, while reserving the ability to continue to work out how or if multi-field subscriptions should be allowed.
Had this limitations been omitted while also not making mention of how to address multi-field subscriptions in the spec, then we would see divergence of behavior and that could tie our hands in the future for deciding how to address these edge cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot @robzhu and @leebyron for the insightful comments! I think now I got a better understanding of the issue. I was also in two minds on this. On one hand, I wanted to get a better understanding about motivation behind inclusion of this rule. But on the other hand, I don't want to delay the progress on subscriptions incision in the spec. I tend to agree that it is a good idea to disallow multiple fields for now and start a separate discussion. I think it is a discussion worth having. I am actually very glad that this point is considered in the spec since I was also quite concerned about the semantics of multiple subscriptions fields.
Can someone help me understand a compelling use case that is served by multi-root subscription operations that would not be equally served by separate individual subscriptions?
In general, I found it very valuable to have as much information from client as possible in single query. For example, the fact that a client can express its requirements for a view or particular part of the application in a single query allowed us to make very interesting optimizations which would be quite hard to do otherwise (it is quite hard to correlate seemingly independent requests/queries). So by allowing client to better express it's requirements with several subscription fields in a single query, we open a door for potential server-side optimizations.
Now that I'm equipped with new insights, I will give it another thought. This thread was definitely helpful in this respect.
Before we will introduce this rule though, I think it is important to consider the nullability of the subscription fields, as i mentioned above. It is possible to make a nullable field not-null later on in a backwards-compatible way. If we allow subscription fields to be not-null now, it might become a challenge in future to allow multiple subscription fields, if we decide to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nullability may mean two different things in this context - this is a great point we should address.
One thing it may mean is that a subscription may not exist given some inputs in a way that isn't considered an error. I think this interpretation is both not what you were referring to, and also probably confusing to think about. The schema talks about the type of the payload result - so we're talking about the types of responses. We should probably make this point in the spec to clarify.
Secondly the nullability of the responses. This is one of the concerns with multi-field subscriptions to address later. For example, should it be legal to have a subscription field streamThings: String?
where it is legal for any payload in the event sequence to in fact be null
? I don't see a compelling reason to explicitly disallow this - though it is an edge case.
I think handling the payloads of multi-field subscriptions will need to account for this
spec/Section 6 -- Execution.md
Outdated
* **Must** support observation of the associated publish stream (for example, | ||
via iteration, callbacks, or reactive semantics). | ||
* **Must** support cancellation of the subscription, see {Unsubscribe}. | ||
* **Must** support a way to identify the subscriber/subscription pair (for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused by what this means even with the additional explanation. What do you mean by "identify the subscriber/subscription pair"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to say "Must support a way to send data to the subscriber."
spec/Section 6 -- Execution.md
Outdated
event carries an optional payload, which we combine with the arguments from | ||
Subscribe() to resolve the selection set. | ||
|
||
* For each {event} and {payload} on {eventStream}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weren't we going to stop calling this "payload"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed this "eventData"
spec/Section 6 -- Execution.md
Outdated
* Let {selectionSet} be the top level Selection Set in {subscription}. | ||
* Let {rootField} be the first top level field in {selectionSet}. | ||
* Let {eventStream} be the result of running {MapSubscriptionEvents(rootField, variableValues)}. | ||
* Optionally: let {data} be the result of running {ExecuteRequest(schema, document, operationName, variableValues, initialValue)}, and return {data} on the subscription object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is initialValue here? What's the value of specifying here and above (lines 166-7) that we can have an initial result? This leads to a lot of questions about how the execution works without any event that we don't address anywhere. Or is there discussion of this elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on this discussion #283, if someone builds subscriptions that can be evaluated without event data, we would not necessarily want that to be in violation of the spec. I've added more text above to clarify the reasoning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this very confusing and probably not what we want. If I understand this correctly, sometimes this algorithm results in a value {data}
which is returned, sometimes it does not - in which case it returns nothing?
Also, this algorithm does not describe what should be done with {eventStream}
once created - I would have expected {eventStream}
to be returned.
As a concrete example - what should I expect to see differently between a subscription which has an initial response vs one that does not, say in terms of websocket payloads? Do I actually get a {}
or {data: null}
payload as an "ACK" for subscriptions without an initial value? Or is "ACK" a detail that belongs to the transport mechanism?
Perhaps another way to frame this is that "initial value" could be a first published event which simply occurs instantaneously after the creation of a subscription?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's valid feedback, but I think merging the initial value into the publish stream might weaken the value proposition here. Let me try to give some context.
Chat room application: we want to subscribe to when users log on or off to keep the list of members in a chatroom up-to-date. But the subscription doesn't give us the set of initial users in the room. So we issue a query for all users in current chat room, and then fire up the subscription. But now we have a small race condition between when we fetched the chat participant list and when the subscription is created where we are no longer "basing" off the right state. It's even possible that the subscription sends its first payload before the query returns. The client-side code to handle this kind of scenario is tricky. Can we do better?
Returning an "initial value" is an attempt to address these sorts of scenarios: in other words, to create a sort of atomic hybrid query/subscribe. @taion, please chime in if I've misrepresented your requirements.
I had proposed that the "initial value" can be tacked onto the "ACK" that comes with successful subscription creation, but there was some push back in the discussion. If it's not part of the ACK, then it will need another protocol message type, with the guarantee that if the initial response is sent at all, it will precede any publish payloads.
If we merge simply set the initial value as the first publish payload, then we'll end up with something like this (from @ rmosolgo):
subscription {
commentCreated(postId: 4) {
# `null` on first request, but present in later updates:
comment {
body
}
# present on first request, but `null` in later updates:
initialPost {
title,
comments {
body
}
}
}
}
Maybe this is fine. I wanted to mention that returning initial value independent of the publish stream would not violate the spec. But you're raising some good questions. Definitely open to suggestions here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had (and still have) the same reaction as @leebyron to this part of the spec. I find it confusing and I don't really understand the value we're adding by explicitly mentioning it. As I mentioned before, this option raises a lot of questions for me that we don't really answer elsewhere. By calling out this "atomic hybrid query/subscribe" operation here, we're making it seem like a central concept for subscriptions (which I don't think it is). What if we leave this out of the spec until we can get more data on the demand for it and the best way to do it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robzhu and I talked about this offline, but I just wanted to point out that this initialValue addition improves but doesn't guarantee a solution to race conditions. Events can still potentially be repeated or dropped between the generation of the initial value payload and subsequent payloads, depending on the system design and the latency of events flowing through it.
I think the kicker here is that most examples of wanting initial data before subscription payloads desire a different query shape - in the example above a list of comments vs an individual comment. The spec as written wouldn't allow for this distinction, so I actually think your example is more powerful while requiring less from the spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upon further discussion with @laneyk and @leebyron, initial value behavior can be supported out-of-the-box using two sub-fields in a subscription, as in @rmosolgo's example. However, the model of an "atomic query/subscribe" is probably better supported via batching. As a result, my next edit will remove any mention of initial value.
To be clear, the resulting edit is that we will simply not mention server behavior with respect to initial value. If you choose to implement/use it, that does not make your server violate the spec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really great, just a few questions and suggestions.
Do you think it would be helpful to include a diagram? I think the RFC content was very helpful to give people a sense of what the text was trying to say, and I don't know if people can really construct an image of what is going on from this spec. Perhaps that's the job of the documentation, though.
|
||
**Explanatory Text** | ||
|
||
Subscription operations must have exactly one root field. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with Rob's text that starting with just one root field is reasonable. A later version of the spec could support multiple fields, but that requires figuring out a lot of edge cases around errors, when subfields are re-executed, and more.
spec/Section 6 -- Execution.md
Outdated
provide a query root object type. If mutations are supported, it must also | ||
provide a mutation root object type. | ||
provide a query root object type. If mutations/subscriptions are supported, it must also | ||
provide a mutation/subscription root object type, respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should be plural:
provide mutation/subscription root object types, respectively
However, I'd be in favor of splitting into two sentences, one about mutations and one about subscriptions.
spec/Section 6 -- Execution.md
Outdated
@@ -143,6 +147,96 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): | |||
selection set. | |||
* Return an unordered map containing {data} and {errors}. | |||
|
|||
### Subscription | |||
|
|||
Unlike queries and mutations, subscriptions have a lifetime that consists of three |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this sentence is going to start with "unlike", it should probably provide the thing we are comparing to, like:
Unlike queries and mutations, which deal with one request and response, subscriptions have a lifetime that consists of three
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's comparing Subscriptions to the other two operation types, so I don't think an example is necessary. I'll reword so that the sentence no longer starts with "unlike".
spec/Section 6 -- Execution.md
Outdated
via iteration, callbacks, or reactive semantics). | ||
* **Must** support cancellation of the subscription, see {Unsubscribe}. | ||
* **Must** support a way to send data to the subscriber. | ||
* **May** include an initial response associated with executing the selection |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the initial response supposed to be distinguishable from a regular response sent immediately after the subscription is started, from the point of view of the client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the initial response should be distinguishable from a publish.
spec/Section 6 -- Execution.md
Outdated
|
||
* **Must** support observation of the associated publish stream (for example, | ||
via iteration, callbacks, or reactive semantics). | ||
* **Must** support cancellation of the subscription, see {Unsubscribe}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using the word "cancel" here comes out of nowhere - maybe "must support unsubscribing" or "must have a way to stop the subscription"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or "cancellation of the observation"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to "cancellation of the observation"
spec/Section 6 -- Execution.md
Outdated
* **Must** support observation of the associated publish stream (for example, | ||
via iteration, callbacks, or reactive semantics). | ||
* **Must** support cancellation of the subscription, see {Unsubscribe}. | ||
* **Must** support a way to send data to the subscriber. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this referring to the event stream? I think this point could be more specific, since it's not clear what "data" is, or which entity is the "subscriber".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The event stream is an abstract concept for the client. All the client sees is a stream of 0-n "publishes", each of which contains a "payload". I feel the use of subscriber is fairly clear here. Opting to keep this as-is unless you feel very strongly otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK I completely misinterpreted this. So "subscriber" refers to the client consuming GraphQL, and "send data" means "send GraphQL results".
spec/Section 6 -- Execution.md
Outdated
*normally* (allowing parallelization). | ||
* Let {errors} be any *field errors* produced while executing the | ||
selection set. | ||
* If {errors} is not empty, optionally, Unsubscribe() the subscription. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this basically just saying the subscription can be terminated at any time by the backend? Or is there something special about terminating in response to a particular kind of error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, from the client's perspective, the subscription could be terminated at any point by the server. Nothing specific here about what sorts of errors result in termination.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems to make this bullet point unnecessary, since it refers to a thing that can happen at this time or any other time, or may not happen at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree w/ @stubailo - since this could also optionally unsubscribe the subscription if {errors}
is empty, this line could be confusing.
I agree this bullet could just be removed here, but somewhere above mention that a subscription's stream of events may end at any moment, perhaps but not always in response to an error on the server
spec/Section 6 -- Execution.md
Outdated
|
||
Rather than mixing stateless (queries and mutations) and stateful | ||
(subscriptions), we recommend keeping the GraphQL server stateless and | ||
delegating all state persistence to these sub-systems. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This almost sounds like it's recommending two schemas. I think what we are trying to say is that one thing should have all of the resolvers (for queries, mutations, and subscriptions), and another thing should have all of the state stuff. Perhaps:
Rather than mixing stateless and stateful logic in the same infrastructure, we recommend keeping your GraphQL execution system stageless, and delegating handling state about event streams and client connections to some other sub-system. In this design, the pub/sub system and client connection gateway call into the stateless execution layer when necessary to resolve GraphQL results. Production concerns like throttling and routing should be handled at the gateway layer to keep that complexity out of your API definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I've reworded this section to try and highlight the main message better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than mixing stateless (queries and mutations) and stateful (subscriptions)
I think this still sounds like the above issue, because it is implying that everything about subscriptions should be separate. When in fact, I think subscription resolvers should probably be defined next to the regular schema, and just the event management and gateway needs to be separate.
@@ -143,6 +147,96 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): | |||
selection set. | |||
* Return an unordered map containing {data} and {errors}. | |||
|
|||
### Subscription |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we include some example responses here, and perhaps an example of a lifecycle for a simple subscription?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, I've added an example after the unsubscribe section.
@stubailo: I think you're right in what you say in that last sentence (emphasis added). |
spec/Section 6 -- Execution.md
Outdated
### Subscription | ||
|
||
If the operation is a subscription, the result of the operation is the creation | ||
of a persistent object on the server that exists for the lifetime of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"object" is a strange word to use in this context, IMHO. Perhaps the creation of a persistent connection?
spec/Section 6 -- Execution.md
Outdated
* **Must** support cancellation of the observation, see {Unsubscribe}. | ||
* **Must** support a way to send data to the subscriber. | ||
* **May** include an initial response associated with executing the selection | ||
set defined on the subscription operation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this list of 4 things confusing and am having trouble understanding what exactly it is that it's describing. Specifically, these things seem to be describing an implementation detail that a client of GraphQL doesn't necessarily interact with or care about. For example, as a client of your GraphQL server, how would I test that you're adhering to these? For point 1, how would I know that the payloads I'm receiving are the associated to a "publish stream"? For point 2, why should I care? I can always just disconnect from the network. For point 3, this is obvious - nowhere else in the spec do we state a requirement that GraphQL must be used along with a way to send data to the requester. Point 4 is out of place as a "may" - it likely belongs elsewhere, probably as part of the "publish" section below.
What about writing lines 157-165 as:
The result of a subscription operation is a sequence of events over time, each event containing the result of executing the provided GraphQL selection on the data associated with that event.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with collapsing the bullet points into the more concise form you have, but I think we'll lose point 4 in the process. I'll address this in your comment further down about initial responses.
spec/Section 6 -- Execution.md
Outdated
data that tells us *who* logged on or off. Without this event data, the | ||
subscription cannot be evaluated. However, it is possible to define | ||
subscriptions that can be evaluated without event data (for example, the current | ||
time on the server, synthetically triggered every second). Subscriptions that do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of this paragraph is describing implementation details, and IMHO is muddying the term "event data" used in context elsewhere. The example about an event triggered every second does contain event data - that event data is the current time on the server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some subscriptions use the event only as a trigger. These kinds of subscriptions can be evaluated whenever, and are capable of returning an initial value.
The "server time" subscription could be modeled either way. For example, if subscription's resolver calls Date.now() rather than expecting the time to come from the event data.
I can reword this section if you still think it's confusing, but I think it's important to call out this sub-class of subscription because this is why we bother broaching the topic of initial result further down.
spec/Section 6 -- Execution.md
Outdated
* Let {selectionSet} be the top level Selection Set in {subscription}. | ||
* Let {rootField} be the first top level field in {selectionSet}. | ||
* Let {eventStream} be the result of running {MapSubscriptionEvents(rootField, variableValues)}. | ||
* Optionally: let {data} be the result of running {ExecuteRequest(schema, document, operationName, variableValues, initialValue)}, and return {data} on the subscription object. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this very confusing and probably not what we want. If I understand this correctly, sometimes this algorithm results in a value {data}
which is returned, sometimes it does not - in which case it returns nothing?
Also, this algorithm does not describe what should be done with {eventStream}
once created - I would have expected {eventStream}
to be returned.
As a concrete example - what should I expect to see differently between a subscription which has an initial response vs one that does not, say in terms of websocket payloads? Do I actually get a {}
or {data: null}
payload as an "ACK" for subscriptions without an initial value? Or is "ACK" a detail that belongs to the transport mechanism?
Perhaps another way to frame this is that "initial value" could be a first published event which simply occurs instantaneously after the creation of a subscription?
spec/Section 6 -- Execution.md
Outdated
|
||
MapSubscriptionEvents(rootField, variableValues): | ||
|
||
* *Application-specific logic to map from root field/variables to events* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be filled in. In particular, nothing is referencing the algorithm in Publish yet, which should probably be referenced here.
Or perhaps there's something missing? By name, I would expect this algorithm to map one set of events to another. Maybe this one should be renamed CreateSubscriptionEvents
or something along those lines which should actually just be a delegation to provided logic, and then separately include MapSubscriptionEvents
which calls into the Publish
algo.
Also - this section on mapping event streams is a great opportunity to mention that while described as an inline algorithm, this step is often implemented across services, with the original event stream belonging to a subscription management service, and the execution happening on an API middleware service.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right, "map" is misleading here. Changed to "CreateSubscriptionEvents" and hooked up the pseudocode from above and below.
spec/Section 6 -- Execution.md
Outdated
event carries an optional payload, which we combine with the arguments from | ||
Subscribe() to resolve the selection set. | ||
|
||
* For each {event} and {eventData} on {eventStream}: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This algo needs a name and arguments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the For each {event} ...
line likely belongs in the MapSubscriptionEvents
such that Publish can be written in a way that is independent to each publish action
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there value to describing {event}
and {eventData}
separately? For example, {event}
is never referenced below - what would be its value?
spec/Section 6 -- Execution.md
Outdated
*normally* (allowing parallelization). | ||
* Let {errors} be any *field errors* produced while executing the | ||
selection set. | ||
* If {errors} is not empty, optionally, Unsubscribe() the subscription. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree w/ @stubailo - since this could also optionally unsubscribe the subscription if {errors}
is empty, this line could be confusing.
I agree this bullet could just be removed here, but somewhere above mention that a subscription's stream of events may end at any moment, perhaps but not always in response to an error on the server
spec/Section 6 -- Execution.md
Outdated
Unsubscribe() | ||
|
||
* Terminate {publishStream} (For example, cancel iteration, detach mapping function) | ||
* Terminate and clean up {eventStream} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole section seems like implementation detail and not really relevant to what is or isn't GraphQL, could it be collapsed into a footnote in the top section on subscriptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked this section.
spec/Section 6 -- Execution.md
Outdated
|
||
MapSubscriptionEvents(rootField, variableValues): | ||
|
||
* *Application-specific logic to map from root field/variables to events* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're right, "map" is misleading here. Changed to "CreateSubscriptionEvents" and hooked up the pseudocode from above and below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking awesome. More fine-grained feedback this time
spec/Section 6 -- Execution.md
Outdated
@@ -103,8 +103,9 @@ Note: This algorithm is very similar to {CoerceArgumentValues()}. | |||
## Executing Operations | |||
|
|||
The type system, as described in the “Type System” section of the spec, must | |||
provide a query root object type. If mutations are supported, it must also | |||
provide a mutation root object type. | |||
provide a query root object type. If mutations or subscriptions are supported, it must also provide a mutation and subscription root object type, respectively. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: line break to maintain roughly 80c?
spec/Section 6 -- Execution.md
Outdated
future events. | ||
|
||
If the operation is a subscription, the result is an event stream called the | ||
"Publish Stream" where each event in the stream is called a "Publish Payload". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flip the order of these two paragraphs.
Also for the para on event streams, some additional detail is necessary around event stream completion. Maybe also some examples for color. Perhaps:
An event stream represents a sequence of discrete events over time which can be observed. As an example, a "Pub-Sub" system may produce an event stream when "subscribing to a topic", with an event occurring on that event stream for each "publish" to that topic. Event streams may produce an infinite sequence of events or may complete at any point. Event streams may complete in response to an error or simply because no more events will occur. An observer may at any point decide to stop observing an event stream, after which it must receive no more events from that event stream.
Note: If an event stream's observer has stopped observing, that may be a good opportunity to clean up any associated resources such as closing any connections which are no longer necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the other paragraph, I'm not sure the events in the stream need a name so much as they need a brief description:
If the operation is a subscription, the result is an event stream called the "Response Stream" where each event in the event stream is the result of executing the operation for each new event on an underlying "Source Stream".
spec/Section 6 -- Execution.md
Outdated
future events. | ||
|
||
If the operation is a subscription, the result is an event stream called the | ||
"Publish Stream" where each event in the stream is called a "Publish Payload". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fear "Publish Stream" may be too ambiguous - it could be too easy to confuse it with the underlying stream. How about: "Source Stream" and "Response Stream"?
spec/Section 6 -- Execution.md
Outdated
|
||
CreateUnderlyingEventStream(rootField, variableValues): | ||
|
||
* *Application-specific logic to map from root field/variables to events* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a reflection of the feedback on graphql/graphql-js#846 - let's break out the top portion of Subscribe
and name this second algorithm "Resolve" since it's resolving from a field.
CreateSourceEventStream(schema, subscription, operationName, variableValues, initialValue)
ResolveFieldEventStream(subscriptionType, rootValue, fieldName, argumentValues)
<- should mirror ResolveFieldValue - look there for an example of getting these values
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the Subscribe()
step is only these two steps, I think you could make a reference to your bottom most section by adding a Note:
to clarify that in many real services, this Subscribe
algorithm is run on a separate service than the execute step, and to refer to the section below on Considerations when supporting subscriptions
spec/Section 6 -- Execution.md
Outdated
Each event in the underlying event stream triggers execution of the subscription | ||
selection set. | ||
|
||
MapEventToPayload(eventStream): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing the arguments referred to below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps MapSourceToResponseEventStream
?
spec/Section 6 -- Execution.md
Outdated
|
||
#### Recommendations and Considerations for Supporting Subscriptions | ||
|
||
Supporting subscriptions is a large change for any GraphQL server. Query and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"significant" instead of "large"?
spec/Section 6 -- Execution.md
Outdated
} | ||
``` | ||
|
||
#### Recommendations and Considerations for Supporting Subscriptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a long section title and it's specifically about scale. So perhaps just Supporting Subscriptions at Scale
?
spec/Section 6 -- Execution.md
Outdated
* Subscription document | ||
* Variables | ||
* Execution context (for example, current logged-in user, locale, etc.) | ||
* Event stream resulting from Subscribe step (above) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this list of things a bit confusing and overly detailed for the point you're trying to make. Perhaps just:
Subscriptions, by contrast, are long-lived and stateful and require maintaining the GraphQL document, variables, and other context over the lifetime of the request.
spec/Section 6 -- Execution.md
Outdated
* Execution context (for example, current logged-in user, locale, etc.) | ||
* Event stream resulting from Subscribe step (above) | ||
|
||
We recommend thinking about the behavior of your system when this state is lost |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spec shouldn't speak in the third person. Instead:
Consider the behavior of your system when state is lost due to the failure of a single machine in a service. Durability and availability may be improved by having a separate dedicated service for managing this state.
spec/Section 6 -- Execution.md
Outdated
recommend keeping the GraphQL server stateless and delegating all state | ||
persistence to sub-systems that are designed for stateful scaling. Note that | ||
subscription types are still defined in the original schema along with queries | ||
and mutations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This note is a bit out of place. You can define this stuff wherever the heck you want, that's implementation detail. Also, this paragraph is really just repeating the point made in the previous one. I think you can probably just remove it entirely if not move a few of the more fine-pointed words into the prior paragraph
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the thorough review! I think I addressed all of your comments.
spec/Section 6 -- Execution.md
Outdated
*normally* (allowing parallelization). | ||
* Let {errors} be any *field errors* produced while executing the | ||
selection set. | ||
* Yield an unordered map containing {publishPayload} and, optionally, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's pretty close after the accumulated edits.
spec/Section 6 -- Execution.md
Outdated
selection set. | ||
* Yield an unordered map containing {publishPayload} and, optionally, | ||
{errors} on {publishStream}. | ||
* At any time while the publish stream is active, the client or server may |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is clearly from the "for each" iteration of {sourceStream}.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting excited! Most feedback here is within the algorithms
spec/Section 6 -- Execution.md
Outdated
#### Subscribe | ||
|
||
Executing a subscription creates a persistent function on the server that | ||
maps an underlying event stream to the Publish Stream. The logic to create the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maps an underlying Source Stream to a returned Response Stream
spec/Section 6 -- Execution.md
Outdated
|
||
Executing a subscription creates a persistent function on the server that | ||
maps an underlying event stream to the Publish Stream. The logic to create the | ||
underlying event stream is application-specific and takes the root field and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to create the Source Stream is application-specific
spec/Section 6 -- Execution.md
Outdated
underlying event stream is application-specific and takes the root field and | ||
query variables as inputs. | ||
|
||
Subscribe(schema, subscription, operationName, variableValues, initialValue): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
operationName
isn't used here. Other related algos like ExecuteQuery
look like:
ExecuteQuery(query, schema, variableValues, initialValue):
So perhaps:
ExecuteSubscription(subscription, schema, variableValues, initialValue):
spec/Section 6 -- Execution.md
Outdated
* Let {subscriptionType} be the root Subscription type in {schema}. | ||
* Assert: {subscriptionType} is an Object type. | ||
* Let {selectionSet} be the top level Selection Set in {subscription}. | ||
* Let {sourceStream} be the result of running {CreateSourceEventStream(schema, subscription, selectionSet, variableValues, initialValue)}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 3 steps above this one should probably be a step in CreateSourceEventStream
. That algo's arguments should mirror that of this one.
spec/Section 6 -- Execution.md
Outdated
* Let {selectionSet} be the top level Selection Set in {subscription}. | ||
* Let {sourceStream} be the result of running {CreateSourceEventStream(schema, subscription, selectionSet, variableValues, initialValue)}. | ||
* Let {responseStream} be the result of running | ||
{MapSourceToResponseEvent(sourceStream)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
one line for this should be fine
spec/Section 6 -- Execution.md
Outdated
{errors}. | ||
* Yield an event containing {response}. | ||
|
||
Note: in large scale subscription systems, ExecuteSelectionSet and Subscribe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: {}
wrapped around algo names so they link in the published html
spec/Section 6 -- Execution.md
Outdated
*normally* (allowing parallelization). | ||
* Let {errors} be any *field errors* produced while executing the | ||
selection set. | ||
* Let {response} be an unordered map containing {publishPayload} and, optionally, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a bit ambiguous since we expect the unordered map to use the key data
but this implies using a key publishPayload
- maybe just rename the variable
spec/Section 6 -- Execution.md
Outdated
Each event in the underlying event stream triggers execution of the subscription | ||
selection set. | ||
|
||
MapSourceToResponseEvent(sourceStream, subscriptionType, selectionSet, variableValues): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably operate on the whole subscription
instead of selectionSet
and schema
instead of subscriptionType
- and pass those through to another algorithm which is a mirror of ExecuteQuery
- that becomes the seam for passing the command to another system. Maybe call it ExecuteSubscriptionEvent
?
spec/Section 6 -- Execution.md
Outdated
|
||
#### Unsubscribe | ||
|
||
Unsubscribe cancels the Publish Stream. This is also a good opportunity for the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cancels the Response Stream?
I took another pass and made changes per your suggestions. Reading everything top-to-bottom, it feels a lot more dense/verbose. We also have some duplication, for example with ExecuteSubscriptionEvent. Do you feel this restructuring buys us consistency/clarity? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some other sections which need some updates:
http://facebook.github.io/graphql/#sec-Language.Operations
http://facebook.github.io/graphql/#sec-Initial-types
http://facebook.github.io/graphql/#sec-Schema-Introspection
http://facebook.github.io/graphql/#sec-Appendix-Grammar-Summary.Query-Document
@@ -143,6 +143,84 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): | |||
selection set. | |||
* Return an unordered map containing {data} and {errors}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also noticed that a step should be added to {ExecuteRequest}
above to handle subscription requests.
spec/Section 6 -- Execution.md
Outdated
Source stream is application-specific and takes the root field and query | ||
variables as inputs. | ||
|
||
Subscribe(subscription, schema, variableValues, initialValue): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be called ExecuteSubscription
to mirror the other algos called by ExecuteRequest
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned that "ExecuteSubscription" and "ExecuteSubscriptionEvent" will be confused with one another. The name seems to imply that the caller can expect a response containing data.
spec/Section 6 -- Execution.md
Outdated
ResolveFieldEventStream(subscriptionType, rootValue, fieldName, argumentValues): | ||
* Let {resolver} be the internal function provided by {subscriptionType} for | ||
determining the resolved value of a field named {fieldName}. | ||
* Return the result of calling {resolver}, providing {rootValue} and {argumentValues}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to add a Note:
below this algorithm mentioning its intentional similarity to {ResolveFieldValue}
spec/Section 6 -- Execution.md
Outdated
determining the resolved value of a field named {fieldName}. | ||
* Return the result of calling {resolver}, providing {rootValue} and {argumentValues}. | ||
|
||
#### Publish Stream |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Response Stream"?
*normally* (allowing parallelization). | ||
* Let {errors} be any *field errors* produced while executing the | ||
selection set. | ||
* Return an unordered map containing {data} and {errors}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, it would be nice to have a Note:
which mentions the intentional similarity to {ExecuteQuery}
spec/Section 6 -- Execution.md
Outdated
|
||
Unsubscribe() | ||
|
||
* Cancel {responseStream} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above we used the term "stop observing" - we should probably be consistent here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the server is the producer of the response stream, if we use "stop observing", we probably give the impression that we are describing client behavior, whereas the previous sections clearly describe server behavior.
spec/Section 6 -- Execution.md
Outdated
the subscription. Here are some example cases in which to Unsubscribe: client | ||
no longer wishes to receive payloads for a subscription; the source event stream | ||
produced an error or naturally ended; the server encountered an error during | ||
ExecuteSubscriptionEvent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you that this is redundant with the note above. I think you should merge the two together, either relying on the Note above and leaving this section with a much simpler explanation of the algorithm below, or removing the Note above and leaving those details here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the note in the "### Subscription" section and left this paragraph in place. I think this reads more clearly after moving the example.
spec/Section 6 -- Execution.md
Outdated
|
||
* Cancel {responseStream} | ||
|
||
#### Example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elsewhere we use **Example**
to avoid creating sections in the TOC for examples. Also, what do you think about moving the Example directly above the algorithms as the last part of the top level "Subscription" section?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great suggestion!
spec/Section 6 -- Execution.md
Outdated
} | ||
``` | ||
|
||
#### Supporting Subscriptions at Scale |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might consider also moving this section above the algorithms and making this a **Title**
instead so it's more directly linked to the discussion of subscriptions.
Yes, I think that it helps to illustrate the seam between creating a subscription and executing each event, which will help those building this across two services. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Back over to you, @leebyron
spec/Section 6 -- Execution.md
Outdated
Source stream is application-specific and takes the root field and query | ||
variables as inputs. | ||
|
||
Subscribe(subscription, schema, variableValues, initialValue): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm concerned that "ExecuteSubscription" and "ExecuteSubscriptionEvent" will be confused with one another. The name seems to imply that the caller can expect a response containing data.
spec/Section 6 -- Execution.md
Outdated
|
||
Unsubscribe() | ||
|
||
* Cancel {responseStream} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the server is the producer of the response stream, if we use "stop observing", we probably give the impression that we are describing client behavior, whereas the previous sections clearly describe server behavior.
spec/Section 6 -- Execution.md
Outdated
the subscription. Here are some example cases in which to Unsubscribe: client | ||
no longer wishes to receive payloads for a subscription; the source event stream | ||
produced an error or naturally ended; the server encountered an error during | ||
ExecuteSubscriptionEvent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the note in the "### Subscription" section and left this paragraph in place. I think this reads more clearly after moving the example.
spec/Section 6 -- Execution.md
Outdated
|
||
* Cancel {responseStream} | ||
|
||
#### Example |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great suggestion!
spec/Section 6 -- Execution.md
Outdated
* If {operation} is a subscription operation: | ||
* If {operation} contains more than one root field, produce a query error. | ||
* Return {operation}. | ||
* Otherwise return {operation}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These new steps aren't necessary. The validation rule will check for this, and the CreateSourceEventStream
already does the right thing of using the first (only) field in the set
spec/Section 2 -- Language.md
Outdated
|
||
* query - a read-only fetch. | ||
* mutation - a write followed by a fetch. | ||
* subscription - a long-lived request that returns data whenever a domain | ||
event triggers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to recycle terms, how about: "a long-lived request that fetches data in response to source events."
This looks awesome. Imminently mergeable. |
This turned out great. |
Nice job with this, @robzhu!!! |
* Subscription validation rule: single root field * typo in explanatory text * Updated wording to match Execution section * Execution section for subscriptions * Remove leftover section on Subscription functions * Incorporate feedback from first review * Line feed after subscription sections * Fix spec syntax * Address wincent's feedback * Modify wording from Laney's feedback * Address Sashko's feedback * Fix inline code snippets * Address Lee's feedback * Address second round of feedback w/ Lee * Address third round of feedback from Lee * Add subscriptions to other sections * Address more feedback on Execution section * Remove extra single root field check and reword subscriptions description * and/or * Minor editing updates * Mini-header for event streams
This PR contains my proposal for formally describing GraphQL Subscriptions in the Spec. It will be accompanied by a PR against GraphQL-js (will provide link when that PR is available).