2. Federated subscriptions
10m

Overview

are that subscribe us to real-time data. And when we combine this with a federated architecture—propagating real-time events across our —the results are even more powerful.

In this lesson, we will:

  • Learn what enable us to do
  • Discuss how work in a federated architecture
  • Distinguish between two protocols: WebSockets and HTTP callbacks

Subscriptions in GraphQL

are all about listening for changing data: we want to know when something changes, and we want to know it as soon as possible.

To do so, the server uses a special connection to notify clients immediately as new events cause the data to change. Queries, by contrast, place the burden on the client to check in every time it wants an update.

Queries are useful when data doesn't change all that often, or when users are perfectly okay with executing a manual request to retrieve the most up-to-date data. For the real-time, mission-critical updates, we'll reach for a new tool: the Subscription type.

The Subscription type

Just like Query and Mutation, Subscription is a root-level type we can add to our schema. It's defined with the type keyword, followed by Subscription with a capital 'S'.

type Subscription {
}

The we give to our Subscription type are "entry points" to our schema, just like the on our Query and Mutation types. We define them the same way too: with a descriptive name (typically describing the kind of event we're subscribing to), any they might accept, and the type that they return.

type Subscription {
listenForMessageInConversation(id: ID!): Message
}

A like listenForMessageInConversation might give the client a way to be notified anytime a new message is received in a particular conversation (indicated by the id ). And as the return type for each event, we'd receive the new Message and could any of its to learn more about it.

Subscriptions in federation

give us a smooth path to data in real time. Let's see how it works in a federated architecture.

Just like with all the queries and a client sends, go straight to our . It receives them as it would any other operation. Then, based on the , the router determines which are responsible for providing the types and included in the .

A diagram showing the router receiving an operation and delegating different pieces to underlying subgraphs

Let's take the following as an example.

Subscribing to new messages in a conversation
subscription SubscribeToMessagesInConversation($id: ID!) {
listenForMessageInConversation(id: $id) {
id
text
sentTime
}
}

This subscribes to a called listenForMessageInConversation, which accepts id, a particular conversation ID. Whenever a new message is sent to the conversation, we'd expect to receive the message's id, text, and sentTime.

, like queries and , are executed based on the 's calculated .

The subscription operation's query plan
QueryPlan {
Subscription {
Primary: {
Fetch(service: "messages") {
{
listenForMessageInConversation(id: $id) {
id
text
sentTime
}
}
}
},
}
}

The starts by determining which is responsible for the top-level in the . So, if listenForMessageInConversation is a provided by the messages , we'll see here from the that the can resolve all of the relevant data in a single trip to the messages .

Subscriptions vs. Queries

Even though the Subscription type is used to retrieve data from the server, it differs from the Query type in an important way.

With queries, we expect to send one request and receive one response. If something changes on the server, we'd need to send a follow-up to get the latest data.

Technically, one exception here is when part of the is deferred, using the @defer . Read up on how deferring data works here.

In contrast, a acts like a "channel" between the client and the (or ). This channel exists to inform us anytime new data becomes available, so that we can use it immediately. (We'll learn more about how this channel is opened in an upcoming lesson!)

A diagram showing how a subscription operation acts more like a channel between router and server

So, we can expect to send this once (a single request) and receive a new response every time our listenForMessageInConversation is impacted (presumably, when someone sends a new message to the conversation we're listening to).

WebSockets and HTTP callbacks

So how do we actually bring to life?

There are two ways the supports bringing into our federated : WebSockets and HTTP callbacks. Let's briefly explore these two methods and discuss their benefits and disadvantages.

WebSockets

WebSockets are used to create a long-lived connection between client and server. This enables the exchange of information without the need for the client to send a new request when it wants to check for updates.

WebSockets connect our clients to an open channel for data to pass through. This connection is bidirectional: meaning that as long as the channel is open, the client can message the server and the server can message the client.

A diagram showing the router and the subgraph bound together in a connection

The primary disadvantage with WebSockets is that we can end up with lots of persistent connections—one for each new client that wishes to subscribe to some data. Unless data changes by the millisecond, these heavy, resource-intensive connections might not be necessary.

When we apply this drawback to in a federated , we have one more consideration: if the is retrieving data from one (or more) , it must also maintain a persistent connection to those subgraphs in addition to the connection with the client. Taken altogether, this keeps all of our data channels open, but at a potentially higher cost.

HTTP callbacks

With HTTP callbacks, a uses a callback URL provided by the to send new data as soon as it becomes available. This process occurs without maintaining a long-lived, resource-intensive connection—instead, the "checks in" periodically with the router.

A diagram showing the router and the subgraph chatting on walkie-talkies

You can picture the process a bit like setting up channels for radio communication:

The participants note where to reach each other and first make sure everything's working ("Do you read me?").

A diagram showing the router and subgraph establishing a channel

They check in periodically with each other ("Are you still there?") and, with any luck, share updates as they happen ("Hey! I've got news!").

A diagram showing the router and subgraph establishing a channel A diagram showing the subgraph sharing an update with the router

When the is terminated, or one of the parties stops responding, the channel shuts down ("Over and out!").

There are three primary roles involved in this process: the , the , and the emitter.

A diagram showing the three primary roles in the process

When the client sends a to the , the router breaks up the and sends the subscription bit to the responsible . It also includes instructions for how the subgraph can "call back" when new data becomes available.

This is where the emitter comes into play: it acts as the mechanism that checks in with the periodically and "emits" new data. The emitter can be an entirely separate service—coordinating with the when there's new data, or otherwise spending its days occasionally checking in with the router to keep the channel alive—but for the purposes of this course, we'll keep our emitter and subgraph one and the same. This means that our will receive the subscription from the router, retrieve the data, and manage the ongoing check-ins with the .

Tip: If WebSockets are like a long phone call between client and server (lots of silence, the occasional update to share), then HTTP callbacks are more like radio check-ins (they know where to reach each other, but client and server touch base just when they need to).

We'll focus on this callback approach for the rest of the course.

Practice

Federated subscriptions
When the router receives a subscription operation, it starts by 
 
. The router determines which subgraph is responsible for the operation's top-level 
 
, and it builds a request to this subgraph. If there are additional fields the subgraph is responsible for providing, they will be 
 
 this request. The router sends requests for data to any other subgraphs involved in the operation 
 
 returning data to the client.

Drag items from this box to the blanks above

  • before

  • subscription field

  • ignored by

  • after

  • Query type

  • included in

  • devising the query plan

  • subgraph schema

  • subscribing to data

  • excluded from

Which of the following options describes the main difference between WebSockets and HTTP callbacks?

Key takeaways

  • The Subscription type is a root-level type like Query and Mutation. We can define its as "entry points" into our schema.
  • When enabled in a federated , clients send to the . The router determines which (s) provide the data for the operation's , and it builds a separate request for each subgraph.
  • Before returning data to the client, the first assembles all of the requested data by resolving all requests to involved . It then returns the entire 's data as a whole.

Up next

Before we can enable the to facilitate our , we need to add the Subscription type to our schema. Let's do that in the next lesson.

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.