From bce18c955c10dadd1c444503b7e960610556ec08 Mon Sep 17 00:00:00 2001 From: Maria Elisabeth Schreiber Date: Thu, 22 Feb 2024 09:22:37 -0700 Subject: [PATCH] docs: update subscription frontmatter (#4693) This PR updates the subscription support article. The current frontmatter and intro suggestion that this feature is for self-hosted routers only. The feature is also available for cloud routers. It also uses shared components introduced in https://github.com/apollographql/docs/pull/783. --------- Co-authored-by: Edward Huang --- .../subscription-support.mdx | 132 ++++++------------ 1 file changed, 44 insertions(+), 88 deletions(-) diff --git a/docs/source/executing-operations/subscription-support.mdx b/docs/source/executing-operations/subscription-support.mdx index 8b53a7ebc0..48c2fae617 100644 --- a/docs/source/executing-operations/subscription-support.mdx +++ b/docs/source/executing-operations/subscription-support.mdx @@ -1,17 +1,19 @@ --- -title: GraphQL subscriptions with a self-hosted router -description: Real-time data with GraphOS Enterprise +title: Configure GraphQL subscription support +subtitle: Enable clients to receive real-time updates +description: Configure your router to support GraphQL subscriptions, enabling clients to receive real-time updates via WebSocket or HTTP callbacks. +minVersion: 1.22.0 --- **For self-hosted routers, subscription support is an [Enterprise feature](../enterprise-features/).** -Subscription support is also available for cloud routers with a GraphOS Serverless or Dedicated plan. [See the docs.](/graphos/operations/subscriptions) +Subscription support is also available for cloud routers with a GraphOS Serverless or Dedicated plan. For cloud router subscription information, refer to [the cloud-specific docs](/graphos/operations/subscriptions). -With [GraphOS Enterprise](/graphos/enterprise/), self-hosted instances of the Apollo Router provide support for GraphQL subscription operations: +GraphOS routers provides support for GraphQL subscription operations: ```graphql subscription OnStockPricesChanged { @@ -32,74 +34,28 @@ type Subscription { ## What are subscriptions for? -GraphQL subscriptions enable clients to receive continual, real-time updates whenever new data becomes available. Unlike queries and mutations, subscriptions are _long-lasting_. This means a client can receive multiple updates from a single subscription: - -```mermaid -sequenceDiagram - participant Client as GraphQL Client - participant Router as Apollo Router - Client->>Router: Initiates subscription - Note over Router: New data available - Router->>Client: Sends new data - Note over Router: New data available - Router->>Client: Sends new data -``` + -Subscriptions are best suited to apps that rely on frequently-changing, time-sensitive data (such as stock prices, IoT sensor readings, live chat, or sports scores). - -## How it works - -```mermaid -flowchart LR; - client(Client); - subgraph "Your infrastructure"; - router(["Apollo
Router"]); - subgraphA[Stocks
subgraph]; - subgraphB[Portfolios
subgraph]; - router-->|"Subscribes
over WebSocket
(or via callback)"|subgraphA; - router-.->|Can query for
entity fields
as needed|subgraphB; - end; - client-->|Subscribes
over HTTP|router; - class client secondary; -``` +## How subscriptions work -1. A client executes a GraphQL subscription operation against your self-hosted router _over HTTP:_ + - ```graphql title="Example subscription" - subscription OnStockPricesChanged { - stockPricesChanged { - symbol - price - } - } - ``` + - - **The client does _not_ use a WebSocket protocol.** Instead, it receives updates via [multipart HTTP responses](./subscription-multipart-protocol/). - - By using HTTP for subscriptions, clients can execute _all_ GraphQL operation types over HTTP instead of using two different protocols. - - [Apollo Client](/react/data/subscriptions#http), [Apollo Kotlin](/kotlin/essentials/subscriptions#configuring-http-subscriptions), and [Apollo iOS](/ios/fetching/subscriptions#http) all support GraphQL subscriptions over HTTP with minimal configuration. See each library's documentation for details. Apollo Client also provides network adapters for the [Relay](/react/data/subscriptions#relay) and [urql](/react/data/subscriptions#urql) libraries. +[Walk through an example.](#example-execution) -2. When your router receives a subscription, it executes that _same_ subscription against whichever subgraph defines the requested field (`stockPricesChanged` in the example above). - - - This communication usually _does_ use [a WebSocket subprotocol](#websocket-setup), for compatibility with most subgraph libraries. - - An HTTP-callback-based protocol is also available. See [HTTP callback setup](#http-callback-setup). - -3. The subgraph periodically sends new data to your router. Whenever it does, the router returns that data to the client in an additional HTTP response "chunk". - - A subscription can include federated entity fields that are defined in _other_ subgraphs. If it does, the router _first_ fetches those fields by querying the corresponding subgraphs (such as **Portfolios** in the diagram above). These queries use HTTP as usual. - -> [Walk through an example.](#example-execution) + ### Special considerations -- Whenever your router updates its supergraph schema at runtime, **it terminates all active subscriptions.** Clients can detect this special-case termination via an error code and execute a new subscription. - - See [Termination on schema update](#termination-on-schema-update). +Whenever your router updates its supergraph schema at runtime, it terminates all active subscriptions. Clients can detect this special-case termination via an error code and execute a new subscription. See [Termination on schema update](#termination-on-schema-update). ## Prerequisites -**Before you add `Subscription` fields to your subgraphs,** do _all_ of the following _in the order shown_ to prevent schema composition errors: +Before you add `Subscription` fields to your subgraphs, do all of the following in the order shown to prevent schema composition errors: -1. Update your Apollo Router instances to version `1.22.0` or later. [Download the latest version.](../quickstart/) - - Previous versions of the Apollo Router don't support subscription operations. +1. Update your router instances to version `1.22.0` or later. [Download the latest version.](../quickstart/) + - Previous versions of the router don't support subscription operations. 1. Make sure your router is [connected to a GraphOS Enterprise organization](../enterprise-features/#enabling-enterprise-features). - Subscription support is an Enterprise feature of self-hosted routers. 1. **If you compose your router's supergraph schema with GraphOS** (instead of with the Rover CLI), [update your build pipeline](/graphos/graphs/updating#2-update-your-build-pipeline) to use Apollo Federation 2.4 or later. @@ -116,7 +72,7 @@ flowchart LR; } ``` - - You can _skip_ modifying subgraph schemas that don't define any `Subscription` fields. + - You can skip modifying subgraph schemas that don't define any `Subscription` fields. 1. If you're using Apollo Server to implement subgraphs, [update your Apollo Server instances to version 4 or later](/apollo-server/migration). - Follow the Apollo Server 4 [guide for enabling subscriptions](/apollo-server/data/subscriptions#enabling-subscriptions). @@ -128,7 +84,7 @@ After you complete these prerequisites, you can safely [configure your router](# After completing all [prerequisites](#prerequisites), in your router's [YAML config file](../configuration/overview/#yaml-config-file), you configure how the router communicates with each of your subgraphs when executing GraphQL subscriptions. -The Apollo Router supports two popular [WebSocket protocols](#websocket-setup) for subscriptions, and it also provides support for an [HTTP-callback-based protocol](#http-callback-setup). Your router must use whichever protocol is expected by each subgraph. +The router supports two popular [WebSocket protocols](#websocket-setup) for subscriptions, and it also provides support for an [HTTP-callback-based protocol](#http-callback-setup). Your router must use whichever protocol is expected by each subgraph. ### WebSocket setup @@ -159,7 +115,7 @@ The router supports the following WebSocket subprotocols, specified via the `pro By default, the router uses the `graphql_ws` protocol option for all subgraphs. You can change this global default and/or override it for individual subgraphs by setting the `protocol` key as shown above. -Your router creates a separate WebSocket connection for each client subscription, _unless_ it can perform [subscription deduplication](#subscription-deduplication). +Your router creates a separate WebSocket connection for each client subscription, unless it can perform [subscription deduplication](#subscription-deduplication). ### HTTP callback setup @@ -168,7 +124,7 @@ Your router creates a separate WebSocket connection for each client subscription * Your router must use whichever subprotocol is expected by each of your subgraphs. * To disambiguate between `graph-ws` and `graph_ws`: * `graph-ws` (with a hyphen `-`) is the name of the [library](https://github.com/enisdenjo/graphql-ws) that uses the recommended `graphql_ws` (with un underscore `_`) WebSocket subprotocol. -* Each `path` must be set as an _absolute path_. For example, given `http://localhost:8080/foo/bar/graphql/ws`, set the absolute path as `path: "/foo/bar/graphql/ws"`. +* Each `path` must be set as an absolute path. For example, given `http://localhost:8080/foo/bar/graphql/ws`, set the absolute path as `path: "/foo/bar/graphql/ws"`. * The `public_url` must include the configured `path` on the router. For example, given a server URL of `http://localhost:8080` and the router's `path` = `/my_callback`, then your `public_url` must append the `path` to the server: `http://localhost:8080/my_callback`. * If you have a proxy in front of the router that redirects queries to the `path` configured in the router, you can specify another path for the `public_url`, for example `http://localhost:8080/external_path`. * Given a `public_url`, the router appends a subscription id to the `public_url` to get {`http://localhost:8080/external_access/{subscription_id}`} then passes it directly to your subgraphs. @@ -176,7 +132,7 @@ Your router creates a separate WebSocket connection for each client subscription -The Apollo Router provides support for receiving subgraph subscription events via HTTP callbacks, _instead of_ over a persistent WebSocket connection. This **callback mode** provides the following advantages over WebSocket-based subscriptions: +The router provides support for receiving subgraph subscription events via HTTP callbacks, instead of over a persistent WebSocket connection. This **callback mode** provides the following advantages over WebSocket-based subscriptions: - The router doesn't need to maintain a persistent connection for each distinct subscription. - You can publish events directly to the router from a pubsub system, instead of routing those events through the subgraph. @@ -240,9 +196,9 @@ In this example, the `reviews` subgraph uses WebSocket and the `accounts` subgra -If you configure both passthrough mode _and_ callback mode for a particular subgraph, the router uses the passthrough mode configuration. +If you configure both passthrough mode and callback mode for a particular subgraph, the router uses the passthrough mode configuration. -If any subgraphs require callback mode, **do not set the `passthrough.all` key**. If you do, the router uses the passthrough mode configuration for _all_ subgraphs. +If any subgraphs require callback mode, **do not set the `passthrough.all` key**. If you do, the router uses the passthrough mode configuration for all subgraphs. @@ -310,18 +266,18 @@ subscription { -- This operation _adds_ the `Product.id` field. The router needs `@key` fields of the `Product` entity to merge entity fields from across subgraphs. -- This operation _removes_ all fields defined in the Reviews subgraph, because the Products subgraph can't resolve them. +- This operation adds the `Product.id` field. The router needs `@key` fields of the `Product` entity to merge entity fields from across subgraphs. +- This operation removes all fields defined in the Reviews subgraph, because the Products subgraph can't resolve them. -At any point after the subscription is initiated, the Products subgraph might send updated data to our router. Whenever this happens, the router _does not_ immediately return this data to the client, because it's missing requested fields from the Reviews subgraph. +At any point after the subscription is initiated, the Products subgraph might send updated data to our router. Whenever this happens, the router does not immediately return this data to the client, because it's missing requested fields from the Reviews subgraph. -Instead, our router executes a standard GraphQL _query_ against the Reviews subgraph to fetch the missing entity fields: +Instead, our router executes a standard GraphQL query against the Reviews subgraph to fetch the missing entity fields: ```graphql query { - _entities(representations: [...]) { + entities(representations: [...]) { ... on Product { reviews { score @@ -335,7 +291,7 @@ After receiving this query result from the Reviews subgraph, our router combines ## Trying subscriptions with `curl` -To quickly try out the Apollo Router's HTTP-based subscriptions _without_ setting up an Apollo Client library, you can execute a `curl` command against your router with the following format: +To quickly try out the GraphOS router's HTTP-based subscriptions without setting up an Apollo Client library, you can execute a `curl` command against your router with the following format: ```bash curl 'http://localhost:4000/' -v \ @@ -372,11 +328,11 @@ For more information on this multipart HTTP subscription protocol, see [this art ## Subscription deduplication -**By default, the Apollo Router deduplicates identical subscriptions.** This can dramatically reduce load on both your router _and_ your subgraphs, because the router doesn't need to open a new connection if an _existing_ connection is already handling the exact same subscription. +**By default, the router deduplicates identical subscriptions.** This can dramatically reduce load on both your router and your subgraphs, because the router doesn't need to open a new connection if an existing connection is already handling the exact same subscription. -For example, if thousands of clients all subscribe to real-time score updates for the same sports game, your router only needs to maintain _one_ connection to your `sportsgames` subgraph to receive events for _all_ of those subscriptions. +For example, if thousands of clients all subscribe to real-time score updates for the same sports game, your router only needs to maintain one connection to your `sportsgames` subgraph to receive events for all of those subscriptions. -The router considers subscription operations **identical** if _all_ of the following are true: +The router considers subscription operations **identical** if all of the following are true: - The operations sent to the subgraph have identical GraphQL selection sets (i.e., requested fields). - The operations provide identical values for all headers that the router sends to the subgraph. @@ -393,17 +349,17 @@ subscription: # highlight-end ``` -Note that this is a _global_ setting (not per-subgraph or per-operation). +Note that this is a global setting (not per-subgraph or per-operation). #### Why disable deduplication? -Disabling deduplication is useful if you _need_ to create a separate connection to your subgraph for each client-initiated subscription. For example: +Disabling deduplication is useful if you need to create a separate connection to your subgraph for each client-initiated subscription. For example: - Your subgraph needs to trigger an important event every time a new client subscribes to its data. - - This event _doesn't_ trigger whenever the router reuses an existing connection. -- Your subscription needs to start by receiving the _first_ value in a particular sequence, instead of the _most recent_ value. - - If a subscription reuses an existing connection, it starts by receiving the next value _for that connection._ - - As a basic example, let's say a subscription should always fire events returning the integers `0` through `1000`, in order. If a new subscription reuses an existing subgraph connection, it starts by receiving whichever value is next for the original connection, which is almost definitely _not_ `0`. + - This event doesn't trigger whenever the router reuses an existing connection. +- Your subscription needs to start by receiving the first value in a particular sequence, instead of the most recent value. + - If a subscription reuses an existing connection, it starts by receiving the next value for that connection. + - As a basic example, let's say a subscription should always fire events returning the integers `0` through `1000`, in order. If a new subscription reuses an existing subgraph connection, it starts by receiving whichever value is next for the original connection, which is almost definitely not `0`. ## Advanced configuration @@ -447,7 +403,7 @@ For example, when your router sends the [`connection_init` message](https://gith } ``` -To specify a _custom_ payload for the`connection_init` message, you can write a [Rhai script](../customizations/rhai/) and use the `context` directly: +To specify a custom payload for the`connection_init` message, you can write a [Rhai script](../customizations/rhai/) and use the `context` directly: ```rhai fn subgraph_service(service, subgraph) { @@ -464,7 +420,7 @@ fn subgraph_service(service, subgraph) { -If you specify both a `context` entry _and_ an `Authorization` header, the `context` entry takes precedence. +If you specify both a `context` entry and an `Authorization` header, the `context` entry takes precedence. @@ -472,7 +428,7 @@ If you specify both a `context` entry _and_ an `Authorization` header, the `cont If your router receives a high volume of events for a particular subscription, it might accumulate a backlog of those events to send to clients. To handle this backlog, the router maintains an in-memory queue of unsent events. -The router maintains a _separate_ event queue for _each_ of its active subscription connections to subgraphs. +The router maintains a separate event queue for each of its active subscription connections to subgraphs. You can configure the size of each event queue in your router's YAML config file, like so: @@ -482,11 +438,11 @@ subscription: queue_capacity: 100000 # Default: 128 ``` -The value of `queue_capacity` corresponds to the _maximum number of subscription events for each queue_, not the _total size_ of those events. +The value of `queue_capacity` corresponds to the maximum number of subscription events for each queue, not the total size of those events. -Whenever your router receives a subscription event when its queue is full, it _discards_ the _oldest_ unsent event in the queue and enqueues the newly received event. The discarded event is _not_ sent to subscribing clients. +Whenever your router receives a subscription event when its queue is full, it discards the oldest unsent event in the queue and enqueues the newly received event. The discarded event is not sent to subscribing clients. -If it's absolutely necessary for clients to receive _every_ subscription event, increase the size of your event queue as needed. +If it's absolutely necessary for clients to receive every subscription event, increase the size of your event queue as needed. ### Limiting the number of client connections