From 4bba743b59e128e8ecb6eb0437fa07acd04c7d40 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 14:28:13 -0400 Subject: [PATCH 01/21] Subscription validation rule: single root field --- spec/Section 5 -- Validation.md | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 4383a76e0..67a02c194 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -198,6 +198,70 @@ query getName { } ``` +### Subscription Operation Definitions + +#### Single root field + +**Formal Specification** + + * For each subscription operation definition {subscription} in the document + * Let {rootFields} be the first level of fields on the operation's selection set. + * {rootFields} must be a set of one. + +**Explanatory Text** + +Subscription operation must have exactly one root field. + +Valid examples: + +```graphql +subscription sub { + newMessage { + body + sender + } +} +``` + +```graphql +fragment newMessageFields on Message { + body + sender +} + +subscription sub { + newMessage { + ... newMessageFields + } +} +``` + +Invalid: + +```!graphql +subscription sub { + newMessage { + body + sender + } + # a second root field will cause this operation to fail validation + alarms { + name + snoozeCount + } +} +``` + +```!graphql +subscription sub { + newMessage { + body + sender + } + # this also applies to introspection fields + __typename +} +``` ## Fields From 7b4fb560ecf4f96c24529f8fe867cbfe2a3e5a97 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 14:29:53 -0400 Subject: [PATCH 02/21] typo in explanatory text --- spec/Section 5 -- Validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 67a02c194..76ebc05c8 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -210,7 +210,7 @@ query getName { **Explanatory Text** -Subscription operation must have exactly one root field. +Subscription operations must have exactly one root field. Valid examples: From 328644b0a211a1cd6f1e7bc605092d8ef0b520df Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 15:02:39 -0400 Subject: [PATCH 03/21] Updated wording to match Execution section --- spec/Section 5 -- Validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 76ebc05c8..ed673b049 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -205,7 +205,7 @@ query getName { **Formal Specification** * For each subscription operation definition {subscription} in the document - * Let {rootFields} be the first level of fields on the operation's selection set. + * Let {rootFields} be the top level selection set on {subscription}. * {rootFields} must be a set of one. **Explanatory Text** From c4aeaf9dacc065f582703c467e0cb04993a9a4f6 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 16:02:04 -0400 Subject: [PATCH 04/21] Execution section for subscriptions --- spec/Section 6 -- Execution.md | 70 ++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index ce5cd4121..69c2e6361 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -143,6 +143,76 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): selection set. * Return an unordered map containing {data} and {errors}. +If the operation is a subscription, the result of the operation is a subscription +object that supports these two operations: + + * observation of the event stream, for example via iteration, callbacks, or + observables. + * unsubscribe, which halts the event stream. + +Unlike queries and mutations, subscriptions have a lifetime that consists of three +phases. Between the the subscribe and unsubscribe phases, the subscription is +considered to be in the "active" state. + +### Subscribe +The result of executing a subscription operation is a subscription object with +the following capabilities: + + * **must** support observation of the associated publish stream (for example, + via iteration, callbacks, or reactive semantics). + * **must** support cancellation of the subscription (aka unsubscribe). + * **must** support a unique mapping to the subscriber (for example, a unique Id). + * **may** include an initial response associated with executing the selection + set defined on the subscription operation. + +Subscribe(subscription, schema, variableValues, initialValue): + + * 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 {rootField} be the first top level field in {selectionSet}. + * Let {eventStream} be a domain-specific transform of {rootField, variableValues} + * Let {publishStream} be a mapping of {eventStream} where each {event} is + mapped via Publish(). + +### Publish +Publish(subscription, schema, variableValues, payload, publishStream) + + * For each {event, payload} in {eventStream} + * Let {data} be the result of running + {ExecuteSelectionSet(selectionSet, subscriptionType, payload, variableValues)} + *normally* (allowing parallelization). + * Let {errors} be any *field errors* produced while executing the + selection set. + * If {errors} is not empty, optionally terminate the subscription. + * Yield an unordered map containing {data} and, optionally, {errors} on + {publishStream}. + +### Unsubscribe +Unsubscribe() + + * Let {publishStream} be a mapping of {null} + +### Recommendations and Considerations for supporting Subscriptions +Supporting subscriptions is a large change for any GraphQL server. Query and +mutation operations are stateless, allowing scaling via cloning GraphQL server +instances. Subscriptions, by contrast, are stateful. The pieces of state for a +subscription are: + + * Subscriber/client channel + * Subscription document + * Variables + * 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 +due to single-node failures. We can improve durability and availability by using +dedicated sub-systems to manage this state. For example, event streams can be +built using modern pub-sub systems, and client channels can be handled with a +dedicate client gateway. + +Rather than mixing stateful and stateless systems, we recommend keeping the +GraphQL server stateless and delegating all state persistence these sub-systems. ## Executing Selection Sets From f38092b9b155af147f521264cd9c11620066e551 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 16:36:21 -0400 Subject: [PATCH 05/21] Remove leftover section on Subscription functions --- spec/Section 6 -- Execution.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 69c2e6361..7d5202f3e 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -143,13 +143,6 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): selection set. * Return an unordered map containing {data} and {errors}. -If the operation is a subscription, the result of the operation is a subscription -object that supports these two operations: - - * observation of the event stream, for example via iteration, callbacks, or - observables. - * unsubscribe, which halts the event stream. - Unlike queries and mutations, subscriptions have a lifetime that consists of three phases. Between the the subscribe and unsubscribe phases, the subscription is considered to be in the "active" state. From ca8af4d3f4ddb6cd447b7ab97d99c36228f3db91 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 17:12:00 -0400 Subject: [PATCH 06/21] Incorporate feedback from first review --- spec/Section 6 -- Execution.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 7d5202f3e..043f50bad 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -154,7 +154,8 @@ the following capabilities: * **must** support observation of the associated publish stream (for example, via iteration, callbacks, or reactive semantics). * **must** support cancellation of the subscription (aka unsubscribe). - * **must** support a unique mapping to the subscriber (for example, a unique Id). + * **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 set defined on the subscription operation. @@ -182,11 +183,16 @@ Publish(subscription, schema, variableValues, payload, publishStream) {publishStream}. ### Unsubscribe +The unsubscribe operation can be implemented in a number of ways. For example, +by using a dedicated subscription manager, defining it as a method on the +subscription object, or cancelling the iterator. + Unsubscribe() * Let {publishStream} be a mapping of {null} + * Terminate and clean up {eventStream} -### Recommendations and Considerations for supporting Subscriptions +### Recommendations and Considerations for Supporting Subscriptions Supporting subscriptions is a large change for any GraphQL server. Query and mutation operations are stateless, allowing scaling via cloning GraphQL server instances. Subscriptions, by contrast, are stateful. The pieces of state for a @@ -202,10 +208,11 @@ We recommend thinking about the behavior of your system when this state is lost due to single-node failures. We can improve durability and availability by using dedicated sub-systems to manage this state. For example, event streams can be built using modern pub-sub systems, and client channels can be handled with a -dedicate client gateway. +dedicated client gateway. -Rather than mixing stateful and stateless systems, we recommend keeping the -GraphQL server stateless and delegating all state persistence these sub-systems. +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. ## Executing Selection Sets From 3933a5aeafcb8019a8c271087351ff6477ef38f2 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 17:34:10 -0400 Subject: [PATCH 07/21] Line feed after subscription sections --- spec/Section 6 -- Execution.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 043f50bad..6d4ed73c1 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -148,6 +148,7 @@ phases. Between the the subscribe and unsubscribe phases, the subscription is considered to be in the "active" state. ### Subscribe + The result of executing a subscription operation is a subscription object with the following capabilities: @@ -170,6 +171,7 @@ Subscribe(subscription, schema, variableValues, initialValue): mapped via Publish(). ### Publish + Publish(subscription, schema, variableValues, payload, publishStream) * For each {event, payload} in {eventStream} @@ -183,6 +185,7 @@ Publish(subscription, schema, variableValues, payload, publishStream) {publishStream}. ### Unsubscribe + The unsubscribe operation can be implemented in a number of ways. For example, by using a dedicated subscription manager, defining it as a method on the subscription object, or cancelling the iterator. @@ -193,6 +196,7 @@ Unsubscribe() * Terminate and clean up {eventStream} ### Recommendations and Considerations for Supporting Subscriptions + Supporting subscriptions is a large change for any GraphQL server. Query and mutation operations are stateless, allowing scaling via cloning GraphQL server instances. Subscriptions, by contrast, are stateful. The pieces of state for a From d3220d6692bbe1ea2efb3d6ce33e3cbebb7cd940 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 18:56:54 -0400 Subject: [PATCH 08/21] Fix spec syntax --- spec/Section 6 -- Execution.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 6d4ed73c1..d6cba4589 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -166,15 +166,19 @@ Subscribe(subscription, schema, variableValues, initialValue): * Assert: {subscriptionType} is an Object type. * Let {selectionSet} be the top level Selection Set in {subscription}. * Let {rootField} be the first top level field in {selectionSet}. - * Let {eventStream} be a domain-specific transform of {rootField, variableValues} + * Let {eventStream} be the result of running {MapSubscriptionEvents(rootField, variableValues)}. * Let {publishStream} be a mapping of {eventStream} where each {event} is mapped via Publish(). +MapSubscriptionEvents(rootField, variableValues): + + * *Application-specific logic to map from root field/variables to events* + ### Publish Publish(subscription, schema, variableValues, payload, publishStream) - * For each {event, payload} in {eventStream} + * For each {eventStream} as {event} and {payload}: * Let {data} be the result of running {ExecuteSelectionSet(selectionSet, subscriptionType, payload, variableValues)} *normally* (allowing parallelization). From e61963c4efc551a14b82df7ad178db1fb78f24fb Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Tue, 2 May 2017 22:32:46 -0400 Subject: [PATCH 09/21] Address wincent's feedback --- spec/Section 5 -- Validation.md | 9 +++---- spec/Section 6 -- Execution.md | 46 +++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index ed673b049..ca75c799e 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -244,21 +244,18 @@ subscription sub { body sender } - # a second root field will cause this operation to fail validation - alarms { - name - snoozeCount - } + disallowedSecondRootField } ``` +Introspection fields are counted. The following example is also invalid: + ```!graphql subscription sub { newMessage { body sender } - # this also applies to introspection fields __typename } ``` diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index d6cba4589..7d5049086 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -103,8 +103,10 @@ 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/subscriptions are supported, it must also +provide a mutation/subscription root object type, respectively. + +### Query If the operation is a query, the result of the operation is the result of executing the query’s top level selection set with the query root object type. @@ -123,6 +125,8 @@ ExecuteQuery(query, schema, variableValues, initialValue): selection set. * Return an unordered map containing {data} and {errors}. +### Mutation + If the operation is a mutation, the result of the operation is the result of executing the mutation’s top level selection set on the mutation root object type. This selection set should be executed serially. @@ -143,52 +147,56 @@ 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 phases. Between the the subscribe and unsubscribe phases, the subscription is considered to be in the "active" state. -### Subscribe +#### Subscribe The result of executing a subscription operation is a subscription object with the following capabilities: - * **must** support observation of the associated publish stream (for example, + * **Must** support observation of the associated publish stream (for example, via iteration, callbacks, or reactive semantics). - * **must** support cancellation of the subscription (aka unsubscribe). - * **must** support a way to identify the subscriber/subscription pair (for + * **Must** support cancellation of the subscription, see {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 + * **May** include an initial response associated with executing the selection set defined on the subscription operation. -Subscribe(subscription, schema, variableValues, initialValue): +Subscribe(schema, subscription, operationName, variableValues, initialValue): * 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 {rootField} be the first top level field in {selectionSet}. * Let {eventStream} be the result of running {MapSubscriptionEvents(rootField, variableValues)}. - * Let {publishStream} be a mapping of {eventStream} where each {event} is - mapped via Publish(). + * Optionally: let {data} be the result of running {ExecuteRequest(schema, document, operationName, variableValues, initialValue)}, and return {data} on the subscription object. MapSubscriptionEvents(rootField, variableValues): * *Application-specific logic to map from root field/variables to events* -### Publish +#### Publish -Publish(subscription, schema, variableValues, payload, publishStream) +Once a subscription is active, we listen for events on its event stream. Each +event carries an optional payload, which we combine with the arguments from +Subscribe() to resolve the selection set. - * For each {eventStream} as {event} and {payload}: + * For each {event} and {payload} on {eventStream}: * Let {data} be the result of running {ExecuteSelectionSet(selectionSet, subscriptionType, payload, variableValues)} *normally* (allowing parallelization). * Let {errors} be any *field errors* produced while executing the selection set. - * If {errors} is not empty, optionally terminate the subscription. + * If {errors} is not empty, optionally, Unsubscribe() the subscription. * Yield an unordered map containing {data} and, optionally, {errors} on {publishStream}. + * Let {publishStream} be the sequence of outputs yielded above. -### Unsubscribe +#### Unsubscribe The unsubscribe operation can be implemented in a number of ways. For example, by using a dedicated subscription manager, defining it as a method on the @@ -196,10 +204,10 @@ subscription object, or cancelling the iterator. Unsubscribe() - * Let {publishStream} be a mapping of {null} + * Terminate {publishStream} (For example, cancel iteration, detach mapping function) * Terminate and clean up {eventStream} -### Recommendations and Considerations for Supporting Subscriptions +#### Recommendations and Considerations for Supporting Subscriptions Supporting subscriptions is a large change for any GraphQL server. Query and mutation operations are stateless, allowing scaling via cloning GraphQL server @@ -209,7 +217,7 @@ subscription are: * Subscriber/client channel * Subscription document * Variables - * Execution context (for example, current logged in user, locale, etc.) + * 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 @@ -220,7 +228,7 @@ dedicated client gateway. 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. +delegating all state persistence to these sub-systems. ## Executing Selection Sets From 6d9dc8d0c80a2861b597a25d9992b6ce1b40a289 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Wed, 3 May 2017 14:46:44 -0400 Subject: [PATCH 10/21] Modify wording from Laney's feedback --- spec/Section 6 -- Execution.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 7d5049086..41a359f32 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -161,11 +161,19 @@ the following capabilities: * **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 - example, a GUID/callback table or closure over the callback). + * **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. +Most subscriptions can only be evaluated when event data is available. For +example, a subscription tells us when users log on and off would require event +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 +not require event data for evaluation can optionally return an initial response +to the Subscribe() operation. + Subscribe(schema, subscription, operationName, variableValues, initialValue): * Let {subscriptionType} be the root Subscription type in {schema}. @@ -185,9 +193,9 @@ Once a subscription is active, we listen for events on its event stream. Each 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}: + * For each {event} and {eventData} on {eventStream}: * Let {data} be the result of running - {ExecuteSelectionSet(selectionSet, subscriptionType, payload, variableValues)} + {ExecuteSelectionSet(selectionSet, subscriptionType, eventData, variableValues)} *normally* (allowing parallelization). * Let {errors} be any *field errors* produced while executing the selection set. From 73b364fd4e40c2c2aef5fff426b27150a0045507 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Thu, 11 May 2017 15:38:00 -0400 Subject: [PATCH 11/21] Address Sashko's feedback --- spec/Section 6 -- Execution.md | 56 ++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 41a359f32..4985aaab3 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -103,8 +103,7 @@ 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/subscriptions are supported, it must also -provide a mutation/subscription root object type, respectively. +provide a query root object type. If mutations or subscriptions are supported, it must also provide a mutation and subscription root object type, respectively. ### Query @@ -149,9 +148,9 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): ### Subscription -Unlike queries and mutations, subscriptions have a lifetime that consists of three -phases. Between the the subscribe and unsubscribe phases, the subscription is -considered to be in the "active" state. +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 +subscription. #### Subscribe @@ -160,7 +159,7 @@ the following capabilities: * **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 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. @@ -189,7 +188,7 @@ MapSubscriptionEvents(rootField, variableValues): #### Publish -Once a subscription is active, we listen for events on its event stream. Each +Once a subscription is created, we listen for events on its event stream. Each event carries an optional payload, which we combine with the arguments from Subscribe() to resolve the selection set. @@ -215,6 +214,35 @@ Unsubscribe() * Terminate {publishStream} (For example, cancel iteration, detach mapping function) * Terminate and clean up {eventStream} +#### Example + +As an example, consider a chat application. To subscribe to new messages posted +to the chat room, the client sends a request like so: + +```graphql +subscription NewMessages { + newMessage(roomId: 123) { + sender + text + } +} +``` + +While the client is subscribe, whenever new messages are posted to chat room +with ```ID 123```, the selection for ```sender``` and ```text``` will be +evaluated and published to the client, for example: + +```js +{ + "data": { + "newMessage": { + "sender": "Hagrid", + "text": "You're a wizard!" + } + } +} +``` + #### Recommendations and Considerations for Supporting Subscriptions Supporting subscriptions is a large change for any GraphQL server. Query and @@ -231,12 +259,14 @@ subscription are: We recommend thinking about the behavior of your system when this state is lost due to single-node failures. We can improve durability and availability by using dedicated sub-systems to manage this state. For example, event streams can be -built using modern pub-sub systems, and client channels can be handled with a -dedicated client gateway. - -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. +built using modern pub-sub systems, and payload delivery to clients can be +handled by a dedicated client gateway tier. + +For systems with high capacity, availability, and durability requirements, we +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. ## Executing Selection Sets From d3011a5d25dfcc6b90faeb0234a65a8123fc70cb Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Thu, 11 May 2017 17:40:53 -0400 Subject: [PATCH 12/21] Fix inline code snippets --- spec/Section 6 -- Execution.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 4985aaab3..0e1fa395a 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -229,8 +229,8 @@ subscription NewMessages { ``` While the client is subscribe, whenever new messages are posted to chat room -with ```ID 123```, the selection for ```sender``` and ```text``` will be -evaluated and published to the client, for example: +with ID "123", the selection for "sender" and "text" will be evaluated and +published to the client, for example: ```js { @@ -265,7 +265,7 @@ handled by a dedicated client gateway tier. For systems with high capacity, availability, and durability requirements, we 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 +subscription types are still defined in the original schema along with queries and mutations. ## Executing Selection Sets From 23bd7907ac1e26cfea95480f7ddf6f9dbd9e3060 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Fri, 12 May 2017 18:36:37 -0400 Subject: [PATCH 13/21] Address Lee's feedback --- spec/Section 6 -- Execution.md | 73 +++++++++++++++------------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 0e1fa395a..79f58fb8a 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -148,30 +148,19 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): ### 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 -subscription. +We define an event stream as a sequence of discrete events over time that can be +observed. An observer of an event stream may cancel observation to avoid stop +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". #### Subscribe -The result of executing a subscription operation is a subscription object with -the following capabilities: - - * **Must** support observation of the associated publish stream (for example, - via iteration, callbacks, or reactive semantics). - * **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. - -Most subscriptions can only be evaluated when event data is available. For -example, a subscription tells us when users log on and off would require event -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 -not require event data for evaluation can optionally return an initial response -to the Subscribe() operation. +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 domain-specific and takes the root field and query +variables as inputs. Subscribe(schema, subscription, operationName, variableValues, initialValue): @@ -179,40 +168,44 @@ Subscribe(schema, subscription, operationName, variableValues, initialValue): * Assert: {subscriptionType} is an Object type. * 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. + * Let {eventStream} be the result of running {CreateUnderlyingEventStream(rootField, variableValues)}. + * Let {publishStream} be the result of running {MapEventToPayload(eventStream)} -MapSubscriptionEvents(rootField, variableValues): +CreateUnderlyingEventStream(rootField, variableValues): * *Application-specific logic to map from root field/variables to events* -#### Publish +#### Publish Stream + +Each event in the underlying event stream triggers execution of the subscription +selection set. -Once a subscription is created, we listen for events on its event stream. Each -event carries an optional payload, which we combine with the arguments from -Subscribe() to resolve the selection set. +MapEventToPayload(eventStream): - * For each {event} and {eventData} on {eventStream}: - * Let {data} be the result of running - {ExecuteSelectionSet(selectionSet, subscriptionType, eventData, variableValues)} + * For each {event} on {eventStream}: + * Let {publishPayload} be the result of running + {ExecuteSelectionSet(selectionSet, subscriptionType, event, variableValues)} *normally* (allowing parallelization). * Let {errors} be any *field errors* produced while executing the selection set. - * If {errors} is not empty, optionally, Unsubscribe() the subscription. - * Yield an unordered map containing {data} and, optionally, {errors} on - {publishStream}. - * Let {publishStream} be the sequence of outputs yielded above. + * 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 + Unsubscribe(). + +Common reasons for Unsubscribe() include: + * client no longer wants to receive payloads for a subscription. + * The underlying event stream has produced an error or has naturally ended. #### Unsubscribe -The unsubscribe operation can be implemented in a number of ways. For example, -by using a dedicated subscription manager, defining it as a method on the -subscription object, or cancelling the iterator. +Unsubscribe cancels the Publish Stream. This is also a good opportunity for the +server to clean up the underlying event stream and any other resources used by +the subscription. Unsubscribe() - * Terminate {publishStream} (For example, cancel iteration, detach mapping function) - * Terminate and clean up {eventStream} + * Cancel {publishStream} #### Example From f7581ef82befd672449920ffb220e54b535f4be6 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Sat, 13 May 2017 01:38:40 -0400 Subject: [PATCH 14/21] Address second round of feedback w/ Lee --- spec/Section 6 -- Execution.md | 108 ++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 79f58fb8a..e2b92902a 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -103,7 +103,8 @@ 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 or subscriptions are supported, it must also provide a mutation and subscription root object type, respectively. +provide a query root object type. If mutations or subscriptions are supported, +it must also provide a mutation and subscription root object type, respectively. ### Query @@ -148,64 +149,87 @@ ExecuteMutation(mutation, schema, variableValues, initialValue): ### Subscription -We define an event stream as a sequence of discrete events over time that can be -observed. An observer of an event stream may cancel observation to avoid stop -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". +"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". + +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. #### 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 -underlying event stream is domain-specific and takes the root field and query -variables as inputs. +underlying event stream is application-specific and takes the root field and +query variables as inputs. Subscribe(schema, subscription, operationName, variableValues, initialValue): * 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 {rootField} be the first top level field in {selectionSet}. - * Let {eventStream} be the result of running {CreateUnderlyingEventStream(rootField, variableValues)}. - * Let {publishStream} be the result of running {MapEventToPayload(eventStream)} + * Let {sourceStream} be the result of running {CreateSourceEventStream(schema, subscription, selectionSet, variableValues, initialValue)}. + * Let {responseStream} be the result of running + {MapSourceToResponseEvent(sourceStream)} + * Return {responseStream}. -CreateUnderlyingEventStream(rootField, variableValues): +CreateSourceEventStream(schema, subscriptionType, selectionSet, variableValues, + initialValue): - * *Application-specific logic to map from root field/variables to events* + * Let {rootField} be the first top level field in {selectionSet}. + * Let {argumentValues} be the result of {CoerceArgumentValues(subscriptionType, rootField, variableValues)}. + * Let {fieldStream} be the result of running {ResolveFieldEventStream(subscriptionType, initialValue, rootField, argumentValues)}. + * Return {fieldStream}. + +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}. #### Publish Stream Each event in the underlying event stream triggers execution of the subscription selection set. -MapEventToPayload(eventStream): +MapSourceToResponseEvent(sourceStream, subscriptionType, selectionSet, variableValues): - * For each {event} on {eventStream}: + * Return a new event stream {publishStream} which yields events as follows: + * For each {event} on {sourceStream}: * Let {publishPayload} be the result of running {ExecuteSelectionSet(selectionSet, subscriptionType, event, variableValues)} *normally* (allowing parallelization). * Let {errors} be any *field errors* produced while executing the 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 - Unsubscribe(). + * Let {response} be an unordered map containing {publishPayload} and, optionally, + {errors}. + * Yield an event containing {response}. -Common reasons for Unsubscribe() include: - * client no longer wants to receive payloads for a subscription. - * The underlying event stream has produced an error or has naturally ended. +Note: in large scale subscription systems, ExecuteSelectionSet and Subscribe +algorithm may be run on separate services to maintain predictable scaling +properties. See the section below on Supporting Subscriptions at Scale. #### Unsubscribe Unsubscribe cancels the Publish Stream. This is also a good opportunity for the server to clean up the underlying event stream and any other resources used by -the subscription. +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 +ExecuteSelectionSet. Unsubscribe() - * Cancel {publishStream} + * Cancel {responseStream} #### Example @@ -236,30 +260,18 @@ published to the client, for example: } ``` -#### Recommendations and Considerations for Supporting Subscriptions - -Supporting subscriptions is a large change for any GraphQL server. Query and -mutation operations are stateless, allowing scaling via cloning GraphQL server -instances. Subscriptions, by contrast, are stateful. The pieces of state for a -subscription are: - - * Subscriber/client channel - * Subscription document - * Variables - * 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 -due to single-node failures. We can improve durability and availability by using -dedicated sub-systems to manage this state. For example, event streams can be -built using modern pub-sub systems, and payload delivery to clients can be -handled by a dedicated client gateway tier. - -For systems with high capacity, availability, and durability requirements, we -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. +#### Supporting Subscriptions at Scale + +Supporting subscriptions is a significant change for any GraphQL server. Query +and mutation operations are stateless, allowing scaling via cloning GraphQL +server instances. Subscriptions, by contrast, are stateful and require +maintaining the GraphQL document, variables, and other context over the lifetime +of the subscription. + +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 separate dedicated services for managing subscription state and client +connectivity. ## Executing Selection Sets From f18c569c76d15f66aa456a2ec2b303d8706274d3 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Sat, 13 May 2017 15:31:58 -0400 Subject: [PATCH 15/21] Address third round of feedback from Lee --- spec/Section 6 -- Execution.md | 57 ++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index e2b92902a..459479783 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -169,23 +169,21 @@ which are no longer necessary. #### 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 -underlying event stream is application-specific and takes the root field and -query variables as inputs. +maps an underlying Source stream to the Publish Stream. The logic to create the +Source stream is application-specific and takes the root field and query +variables as inputs. -Subscribe(schema, subscription, operationName, variableValues, initialValue): +Subscribe(subscription, schema, variableValues, initialValue): - * 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)}. - * Let {responseStream} be the result of running - {MapSourceToResponseEvent(sourceStream)} + * Let {sourceStream} be the result of running {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. + * Let {responseStream} be the result of running {MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues)} * Return {responseStream}. -CreateSourceEventStream(schema, subscriptionType, selectionSet, variableValues, - initialValue): +CreateSourceEventStream(subscription, schema, variableValues, initialValue): + * 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 {rootField} be the first top level field in {selectionSet}. * Let {argumentValues} be the result of {CoerceArgumentValues(subscriptionType, rootField, variableValues)}. * Let {fieldStream} be the result of running {ResolveFieldEventStream(subscriptionType, initialValue, rootField, argumentValues)}. @@ -201,31 +199,38 @@ ResolveFieldEventStream(subscriptionType, rootValue, fieldName, argumentValues): Each event in the underlying event stream triggers execution of the subscription selection set. -MapSourceToResponseEvent(sourceStream, subscriptionType, selectionSet, variableValues): +MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): - * Return a new event stream {publishStream} which yields events as follows: + * Return a new event stream {responseStream} which yields events as follows: * For each {event} on {sourceStream}: - * Let {publishPayload} be the result of running - {ExecuteSelectionSet(selectionSet, subscriptionType, event, variableValues)} - *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, - {errors}. + * Let {response} be the result of running + {ExecuteSubscriptionEvent(subscription, schema, variableValues, event)}. * Yield an event containing {response}. -Note: in large scale subscription systems, ExecuteSelectionSet and Subscribe -algorithm may be run on separate services to maintain predictable scaling -properties. See the section below on Supporting Subscriptions at Scale. +ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): + + * 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 {data} be the result of running + {ExecuteSelectionSet(selectionSet, subscriptionType, initialValue, variableValues)} + *normally* (allowing parallelization). + * Let {errors} be any *field errors* produced while executing the + selection set. + * Return an unordered map containing {data} and {errors}. + +Note: in large scale subscription systems, the {ExecuteSubscriptionEvent} and +{Subscribe} algorithms may be run on separate services to maintain predictable +scaling properties. See the section below on Supporting Subscriptions at Scale. #### Unsubscribe -Unsubscribe cancels the Publish Stream. This is also a good opportunity for the +Unsubscribe cancels the Response Stream. This is also a good opportunity for the server to clean up the underlying event stream and any other resources used by 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 -ExecuteSelectionSet. +ExecuteSubscriptionEvent. Unsubscribe() From a0aac7f1f95aa4c207d974b1dd698c8756e0c52f Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Mon, 15 May 2017 01:25:53 -0400 Subject: [PATCH 16/21] Add subscriptions to other sections --- spec/Appendix B -- Grammar Summary.md | 2 +- spec/Section 2 -- Language.md | 8 +++++--- spec/Section 3 -- Type System.md | 23 ++++++++++++++++++----- spec/Section 4 -- Introspection.md | 2 ++ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index b1a79f703..d3b59f131 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -94,7 +94,7 @@ OperationDefinition : - SelectionSet - OperationType Name? VariableDefinitions? Directives? SelectionSet -OperationType : one of query mutation +OperationType : one of query mutation subscription SelectionSet : { Selection+ } diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 6e24c7950..d6a8602c1 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -2,7 +2,7 @@ Clients use the GraphQL query language to make requests to a GraphQL service. We refer to these request sources as documents. A document may contain -operations (queries and mutations are both operations) as well as fragments, a +operations (queries, mutations, and subscriptions) as well as fragments, a common unit of composition allowing for query reuse. A GraphQL document is defined as a syntactic grammar where terminal symbols are @@ -193,12 +193,14 @@ OperationDefinition : - OperationType Name? VariableDefinitions? Directives? SelectionSet - SelectionSet -OperationType : one of `query` `mutation` +OperationType : one of `query` `mutation` `subscription` -There are two types of operations that GraphQL models: +There are three types of operations that GraphQL models: * 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. Each operation is represented by an optional operation name and a selection set. diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 49d761bd7..6941e20d8 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -12,8 +12,8 @@ A given GraphQL schema must itself be internally valid. This section describes the rules for this validation process where relevant. A GraphQL schema is represented by a root type for each kind of operation: -query and mutation; this determines the place in the type system where those -operations begin. +query, mutation, and subscription; this determines the place in the type system +where those operations begin. All types within a GraphQL schema must have unique names. No two provided types may have the same name. No provided type may have a name which conflicts with @@ -1013,12 +1013,14 @@ must *not* be queried if either the `@skip` condition is true *or* the ## Initial types -A GraphQL schema includes types, indicating where query and mutation -operations start. This provides the initial entry points into the +A GraphQL schema includes types, indicating where query, mutation, and +subscription operations start. This provides the initial entry points into the type system. The query type must always be provided, and is an Object base type. The mutation type is optional; if it is null, that means the system does not support mutations. If it is provided, it must -be an object base type. +be an object base type. Similarly, the subscription type is optional; if it is +null, the system does not support subscriptions. If it is provided, it must be +an object base type. The fields on the query type indicate what fields are available at the top level of a GraphQL query. For example, a basic GraphQL query @@ -1043,3 +1045,14 @@ mutation setName { Is valid when the type provided for the mutation starting type is not null, and has a field named "setName" with a string argument named "name". + +```graphql +subscription { + newMessage { + text + } +} +``` + +Is valid when the type provided for the subscription starting type is not null, +and has a field named "newMessage" and only contains a single root field. diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index e6a453eaa..52d73a9b8 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -124,6 +124,7 @@ type __Schema { types: [__Type!]! queryType: __Type! mutationType: __Type + subscriptionType: __Type directives: [__Directive!]! } @@ -195,6 +196,7 @@ type __Directive { enum __DirectiveLocation { QUERY MUTATION + SUBSCRIPTION FIELD FRAGMENT_DEFINITION FRAGMENT_SPREAD From 35c6059298b098c1d5766930ca7db060a5479ba8 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Mon, 15 May 2017 11:23:43 -0400 Subject: [PATCH 17/21] Address more feedback on Execution section --- spec/Section 6 -- Execution.md | 108 ++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 50 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 459479783..22fc384df 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -34,12 +34,18 @@ ExecuteRequest(schema, document, operationName, variableValues, initialValue): * Return {ExecuteQuery(operation, schema, coercedVariableValues, initialValue)}. * Otherwise if {operation} is a mutation operation: * Return {ExecuteMutation(operation, schema, coercedVariableValues, initialValue)}. + * Otherwise if {operation} is a subscription operation: + * Return {Subscribe(operation, schema, coercedVariableValues, initialValue)}. GetOperation(document, operationName): * If {operationName} is {null}: * If {document} contains exactly one operation. - * Return the Operation contained in the {document}. + * Let {operation} be the Operation contained in the {document}. + * If {operation} is a subscription operation: + * If {operation} contains more than one root field, produce a query error. + * Return {operation}. + * Otherwise return {operation}. * Otherwise produce a query error requiring {operationName}. * Otherwise: * Let {operation} be the Operation named {operationName} in {document}. @@ -162,14 +168,53 @@ 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. +As an example, consider a chat application. To subscribe to new messages posted +to the chat room, the client sends a request like so: + +```graphql +subscription NewMessages { + newMessage(roomId: 123) { + sender + text + } +} +``` + +While the client is subscribed, whenever new messages are posted to chat room +with ID "123", the selection for "sender" and "text" will be evaluated and +published to the client, for example: + +```js +{ + "data": { + "newMessage": { + "sender": "Hagrid", + "text": "You're a wizard!" + } + } +} +``` + +The "new message posted to chat room" could use a "Pub-Sub" system where the +chat room ID is the "topic" and each "publish" contains the sender and text. + +**Supporting Subscriptions at Scale** + +Supporting subscriptions is a significant change for any GraphQL server. Query +and mutation operations are stateless, allowing scaling via cloning of GraphQL +server instances. Subscriptions, by contrast, are stateful and require +maintaining the GraphQL document, variables, and other context over the lifetime +of the subscription. + +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 separate dedicated services for managing subscription state and client +connectivity. #### Subscribe Executing a subscription creates a persistent function on the server that -maps an underlying Source stream to the Publish Stream. The logic to create the +maps an underlying Source stream to the Response Stream. The logic to create the Source stream is application-specific and takes the root field and query variables as inputs. @@ -194,7 +239,10 @@ ResolveFieldEventStream(subscriptionType, rootValue, fieldName, argumentValues): determining the resolved value of a field named {fieldName}. * Return the result of calling {resolver}, providing {rootValue} and {argumentValues}. -#### Publish Stream +Note: this algorithm is intentionally similar to {ResolveFieldValue} to enable +consistency when defining resolvers on any operation type. + +#### Response Stream Each event in the underlying event stream triggers execution of the subscription selection set. @@ -221,7 +269,9 @@ ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): Note: in large scale subscription systems, the {ExecuteSubscriptionEvent} and {Subscribe} algorithms may be run on separate services to maintain predictable -scaling properties. See the section below on Supporting Subscriptions at Scale. +scaling properties. See the section above on Supporting Subscriptions at Scale. +This algorithm is intentionally similar to {ExecuteQuery} since this is where +the subscription's selection set is executed. #### Unsubscribe @@ -230,54 +280,12 @@ server to clean up the underlying event stream and any other resources used by 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. +{ExecuteSubscriptionEvent}. Unsubscribe() * Cancel {responseStream} -#### Example - -As an example, consider a chat application. To subscribe to new messages posted -to the chat room, the client sends a request like so: - -```graphql -subscription NewMessages { - newMessage(roomId: 123) { - sender - text - } -} -``` - -While the client is subscribe, whenever new messages are posted to chat room -with ID "123", the selection for "sender" and "text" will be evaluated and -published to the client, for example: - -```js -{ - "data": { - "newMessage": { - "sender": "Hagrid", - "text": "You're a wizard!" - } - } -} -``` - -#### Supporting Subscriptions at Scale - -Supporting subscriptions is a significant change for any GraphQL server. Query -and mutation operations are stateless, allowing scaling via cloning GraphQL -server instances. Subscriptions, by contrast, are stateful and require -maintaining the GraphQL document, variables, and other context over the lifetime -of the subscription. - -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 separate dedicated services for managing subscription state and client -connectivity. - ## Executing Selection Sets To execute a selection set, the object value being evaluated and the object type From ca2a02e134f59db09d5f42c3bc27ab8dce485335 Mon Sep 17 00:00:00 2001 From: Robert Zhu Date: Wed, 17 May 2017 13:09:34 -0400 Subject: [PATCH 18/21] Remove extra single root field check and reword subscriptions description --- spec/Section 2 -- Language.md | 4 ++-- spec/Section 6 -- Execution.md | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index d6a8602c1..15b95849a 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -199,8 +199,8 @@ There are three types of operations that GraphQL models: * 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. + * subscription - a long-lived request that fetches data in response to source + events. Each operation is represented by an optional operation name and a selection set. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 22fc384df..a340787ea 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -41,11 +41,7 @@ GetOperation(document, operationName): * If {operationName} is {null}: * If {document} contains exactly one operation. - * Let {operation} be the Operation contained in the {document}. - * If {operation} is a subscription operation: - * If {operation} contains more than one root field, produce a query error. - * Return {operation}. - * Otherwise return {operation}. + * Return the Operation contained in the {document}. * Otherwise produce a query error requiring {operationName}. * Otherwise: * Let {operation} be the Operation named {operationName} in {document}. From 36359fd88cb50cb4b6528df44df18e07c1675ed5 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 17 May 2017 23:48:22 +0200 Subject: [PATCH 19/21] and/or --- spec/Section 6 -- Execution.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index a340787ea..ae1bade99 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -106,7 +106,7 @@ Note: This algorithm is very similar to {CoerceArgumentValues()}. The type system, as described in the “Type System” section of the spec, must provide a query root object type. If mutations or subscriptions are supported, -it must also provide a mutation and subscription root object type, respectively. +it must also provide a mutation or subscription root object type, respectively. ### Query From 47404cb687ec5a7a1d74e02d9a97fb19d1617e77 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Thu, 18 May 2017 00:05:53 +0200 Subject: [PATCH 20/21] Minor editing updates --- spec/Section 6 -- Execution.md | 63 ++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index ae1bade99..c8501dc65 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -155,14 +155,27 @@ 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". +Executing a subscription creates a persistent function on the server that +maps an underlying Source Stream to a returned Response Stream. + +Subscribe(subscription, schema, variableValues, initialValue): + + * Let {sourceStream} be the result of running {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. + * Let {responseStream} be the result of running {MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues)} + * Return {responseStream}. + +Note: In large scale subscription systems, the {Subscribe} and {ExecuteSubscriptionEvent} +algorithms may be run on separate services to maintain predictable scaling +properties. See the section below on Supporting Subscriptions at Scale. + 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. +point decide to stop observing an event stream by cancelling it, after which it +must receive no more events from that event stream. As an example, consider a chat application. To subscribe to new messages posted to the chat room, the client sends a request like so: @@ -207,18 +220,11 @@ single machine in a service. Durability and availability may be improved by having separate dedicated services for managing subscription state and client connectivity. -#### Subscribe +#### Source Stream -Executing a subscription creates a persistent function on the server that -maps an underlying Source stream to the Response Stream. The logic to create the -Source stream is application-specific and takes the root field and query -variables as inputs. - -Subscribe(subscription, schema, variableValues, initialValue): - - * Let {sourceStream} be the result of running {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. - * Let {responseStream} be the result of running {MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues)} - * Return {responseStream}. +A Source Stream represents the sequence of events, each of which will +trigger a GraphQL execution corresponding to that event. Like field value +resolution, the logic to create a Source Stream is application-specific. CreateSourceEventStream(subscription, schema, variableValues, initialValue): @@ -232,16 +238,17 @@ CreateSourceEventStream(subscription, schema, variableValues, initialValue): ResolveFieldEventStream(subscriptionType, rootValue, fieldName, argumentValues): * Let {resolver} be the internal function provided by {subscriptionType} for - determining the resolved value of a field named {fieldName}. + determining the resolved event stream of a subscription field named {fieldName}. * Return the result of calling {resolver}, providing {rootValue} and {argumentValues}. -Note: this algorithm is intentionally similar to {ResolveFieldValue} to enable -consistency when defining resolvers on any operation type. +Note: This {ResolveFieldEventStream} algorithm is intentionally similar +to {ResolveFieldValue} to enable consistency when defining resolvers +on any operation type. #### Response Stream -Each event in the underlying event stream triggers execution of the subscription -selection set. +Each event in the underlying Source Stream triggers execution of the subscription +selection set using that event as a root value. MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): @@ -250,6 +257,7 @@ MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues): * Let {response} be the result of running {ExecuteSubscriptionEvent(subscription, schema, variableValues, event)}. * Yield an event containing {response}. + * When {responseStream} completes: complete this event stream. ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): @@ -263,22 +271,17 @@ ExecuteSubscriptionEvent(subscription, schema, variableValues, initialValue): selection set. * Return an unordered map containing {data} and {errors}. -Note: in large scale subscription systems, the {ExecuteSubscriptionEvent} and -{Subscribe} algorithms may be run on separate services to maintain predictable -scaling properties. See the section above on Supporting Subscriptions at Scale. -This algorithm is intentionally similar to {ExecuteQuery} since this is where -the subscription's selection set is executed. +Note: The {ExecuteSubscriptionEvent} algorithm is intentionally similar to +{ExecuteQuery} since this is how the each event result is produced. #### Unsubscribe -Unsubscribe cancels the Response Stream. This is also a good opportunity for the -server to clean up the underlying event stream and any other resources used by -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}. +Unsubscribe cancels the Response Stream when a client no longer wishes to receive +payloads for a subscription. This may in turn also cancel the Source Stream. +This is also a good opportunity to clean up any other resources used by +the subscription. -Unsubscribe() +Unsubscribe(responseStream) * Cancel {responseStream} From 6425fed4ec54e5557b32d30eb66d539eb5320d84 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Thu, 18 May 2017 00:07:14 +0200 Subject: [PATCH 21/21] Mini-header for event streams --- spec/Section 6 -- Execution.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index c8501dc65..6a34046a7 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -168,6 +168,8 @@ Note: In large scale subscription systems, the {Subscribe} and {ExecuteSubscript algorithms may be run on separate services to maintain predictable scaling properties. See the section below on Supporting Subscriptions at Scale. +**Event Streams** + 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