Skip to content

Commit

Permalink
docs: update subscription frontmatter (#4693)
Browse files Browse the repository at this point in the history
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
apollographql/docs#783.

---------

Co-authored-by: Edward Huang <edward.huang@apollographql.com>
  • Loading branch information
Meschreiber and shorgi authored Feb 22, 2024
1 parent 23656ea commit bce18c9
Showing 1 changed file with 44 additions and 88 deletions.
132 changes: 44 additions & 88 deletions docs/source/executing-operations/subscription-support.mdx
Original file line number Diff line number Diff line change
@@ -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
---

<PremiumFeature>

**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).

</PremiumFeature>

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 {
Expand All @@ -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
```
<WhatSubscriptionsAreFor />

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 "<b>Your infrastructure</b>";
router(["Apollo<br/>Router"]);
subgraphA[Stocks<br/>subgraph];
subgraphB[Portfolios<br/>subgraph];
router-->|"Subscribes<br/>over WebSocket<br/>(or via callback)"|subgraphA;
router-.->|Can query for<br/>entity fields<br/>as needed|subgraphB;
end;
client-->|Subscribes<br/>over HTTP|router;
class client secondary;
```
## How subscriptions work

1. A client executes a GraphQL subscription operation against your self-hosted router _over HTTP:_
<HowSubscriptionsWork />

```graphql title="Example subscription"
subscription OnStockPricesChanged {
stockPricesChanged {
symbol
price
}
}
```
<Tip>

- **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)
</Tip>

### 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.
Expand All @@ -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).
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand All @@ -168,15 +124,15 @@ 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.
* If you don't specify the `path`, its default value is `/callback`, so you'll have to specify it in `public_url`.

</Note>

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.
Expand Down Expand Up @@ -240,9 +196,9 @@ In this example, the `reviews` subgraph uses WebSocket and the `accounts` subgra

<Caution>

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.

</Caution>

Expand Down Expand Up @@ -310,18 +266,18 @@ subscription {

<Note>

- 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.

</Note>

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
Expand All @@ -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 \
Expand Down Expand Up @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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) {
Expand All @@ -464,15 +420,15 @@ fn subgraph_service(service, subgraph) {

<Note>

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.

</Note>

### Expanding event queue capacity

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:

Expand All @@ -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

Expand Down

0 comments on commit bce18c9

Please sign in to comment.