Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] docs: router lifecycle #6497

Draft
wants to merge 4 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions docs/shared/router-lifecycle-services.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
A router's request lifecycle has three major services:
A router's request lifecycle has three major services that support instrumentation:

* **Router service** - Handles an incoming request before it is parsed. Works within a context of opaque bytes.
* **Supergraph service** - Handles a request after it has been parsed and before it is sent to the subgraph. Works within a GraphQL context.
* **Subgraph service** - Handles a request after it has been sent to the subgraph. Works within a GraphQL context.
* **Router service** - Handles the opaque bytes of an incoming HTTP request before it's parsed into a GraphQL request. Operates within an HTTP server.
* **Supergraph service** - Handles a GraphQL request after it has been parsed and before it is sent to subgraphs. Runs the query planner to produce a query plan to execute.
* **Subgraph service** - Handles GraphQL subgraph requests that have been executed as part of a query plan. Creates HTTP client requests to subgraphs.

<Note>

The router's **Execution service** that executes query plans doesn't support instrumentation.

</Note>
304 changes: 304 additions & 0 deletions docs/source/routing/request-lifecycle.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
---
title: Router Request Lifecycle
subtitle: Understand how the router processes client requests
description: Understand how GraphQL client requests get processed by the request lifecycle pipeline of Apollo GraphOS Router.
---

## The request lifecycle

Every client request made to a GraphOS Router goes through its **router request lifecycle**: a multi-stage pipeline of services that processes requests and returns responses.

```mermaid
flowchart RL
subgraph client["Client"]
end

subgraph router["Router"]
direction LR
routerService("Router <br/> Service")
supergraphService("Supergraph <br/> Service")
executionService("Execution <br/> Service")
subgraphService("Subgraph <br/> Service")
routerService -->|request| supergraphService -->|request| executionService -->|request| subgraphService
subgraphService -->|response| executionService -->|response| supergraphService -->|response| routerService

end

subgraph infra["Your infrastructure"]
direction TB
api1("subgraph A");
api2("subgraph B");
api3("subgraph C");
api1 --- api2 --- api3

end

infra -->|response| router -->|response| client

client -->|request| router -->|request| infra
```

A client request is processed first by passing it between services along the lifecycle's **request path** to create and send its constituent subgraph requests. Then all the responses from the subgraph requests are gathered into a client response along the lifecycle's **response path**.

## Request path

In the request path, each request is processed by the request lifecycle services in the following order:

* The **Router service** receives the client request from the HTTP server and parses it into a GraphQL operation.
* The **Supergraph service** receives a GraphQL operation and calls the router's query planner to produce the query plan that most efficiently executes the operation.
* The **Execution service** executes a query plan by calling the necessary subgraph services to make subgraph requests
* Each subgraph has an associated **Subgraph service** that makes HTTP requests to the subgraph.

The following diagram and its steps describe the request path in further detail:

```mermaid
flowchart TB;
client(Client);
subgraph router["Router"]
direction LR
httpServer("HTTP server")
subgraph routerService["Router Service"]
routerPlugins[[Router plugins]];
end
subgraph " "
subgraph supergraphService["Supergraph Service"]
supergraphPlugins[[Supergraph plugins]];
end
queryPlanner("Query Planner");
end


subgraph executionService["Execution Service"]
executionPlugins[[Execution plugins]];
end

subgraph subgraphService["Subgraph Services"]
subgraph service1["Subgraph Service A"]
subgraphPlugins1[[Subgraph plugins]];
end
subgraph service2["Subgraph Service B"]
subgraphPlugins2[[Subgraph plugins]];
end
end
end;
subgraphA[Subgraph A];
subgraphB[Subgraph B];

client --"1. HTTP request"--> httpServer;
httpServer --"2. <code>RouterRequest</code>"--> routerService;
routerService --"3. <code>SupergraphRequest</code>"--> supergraphService
supergraphService --"4. Query"--> queryPlanner;
queryPlanner --"5. Query plan"--> supergraphService;
supergraphService --"6. <code>ExecutionRequest</code>"--> executionService;

executionService --"7a. <code>SubgraphRequest</code>"--> service1;
executionService --"7b. <code>SubgraphRequest</code>"--> service2;

service1 --"8a. HTTP request"--> subgraphA;
service2 --"8b. HTTP request"--> subgraphB;
```

1. The router receives a client request at an HTTP server.
2. The HTTP server transforms the HTTP request into a `RouterRequest` containing HTTP headers and the request body as a stream of byte arrays.
3. The router service receives the `RouterRequest`. It handles Automatic Persisted Queries (APQ), parses the GraphQL request from JSON, and calls the supergraph service with the resulting `SupergraphRequest`.
4. The supergraph service calls the query planner with the GraphQL query from the `SupergraphRequest`.
5. The query planner returns a query plan for most efficiently executing the query.
6. The supergraph service calls the execution service with an `ExecutionRequest`, made up of `SupergraphRequest` and the query plan.
7. For each fetch node of the query plan, the execution service creates a `SubgraphRequest` and then calls the respective subgraph service.
8. Each subgraph has its own subgraph service, and each service can have its own subgraph plugin configuration. The subgraph service transforms the `SubgraphRequest` into an HTTP request to its subgraph. The `SubgraphRequest` contains:
- the (read-only) `SupergraphRequest`
- HTTP headers
- the subgraph request's operation type (query, mutation, or subscription)
- a GraphQL request object as the request body

Once your subgraphs provide a response, the response follows the response path.

## Response path

In the response path, the subgraph responses are gathered into a client response by the lifecycle services in the following order:

* The **Execution service** receives and formats all subgraph responses.
* The **Supergraph service** gathers the content of all subgraph responses into stream.
* The **Router service** serializes the stream of responses into JSON and forwards it to the HTTP server to send it to the client.

The following diagram and its steps describe the response path in further detail:

```mermaid
flowchart BT;
client(Client);
subgraph " "
direction LR
httpServer("HTTP server")
subgraph routerService["Router Service"]
routerPlugins[[Router plugins]];
end
subgraph " "
subgraph supergraphService["Supergraph Service"]
supergraphPlugins[[Supergraph plugins]];
end
queryPlanner("QueryPlanner");
end


subgraph executionService["Execution Service"]
executionPlugins[[Execution plugins]];
end

subgraph subgraphService["Subgraph Services"]
subgraph service1["Subgraph Service A"]
subgraphPlugins1[[Subgraph plugins]];
end
subgraph service2["Subgraph Service B"]
subgraphPlugins2[[Subgraph plugins]];
end
end
end;
subgraph1[Subgraph A];
subgraph2[Subgraph B];

subgraph1 -- "9a. HTTP response"--> service1;
subgraph2 -- "9b. HTTP response"--> service2;
service1 --"10a. <code>SubgraphResponse</code>"--> executionService;
service2 --"10b. <code>SubgraphResponse</code>"--> executionService;
executionService --"11. <code>ExecutionResponse</code>"--> supergraphService;
supergraphService --"12. <code>SupergraphResponse</code>"--> routerService;
routerService --"13. <code>RouterResponse</code>"--> httpServer;
httpServer --"14. HTTP response" --> client
```
9. Each subgraph provides an HTTP response to the subgraph services.
10. Each subgraph service creates a `SubgraphResponse` containing the HTTP headers and a GraphQL response.
11. Once the execution service has received all subgraph responses, it formats the GraphQL responses—removing unneeded data and propagating nulls—before sending it back to the supergraph plugin as the `ExecutionResponse`.
12. The `SupergraphResponse` has the same content as the `ExecutionResponse`. It contains headers and a stream of GraphQL responses. That stream only contains one element for most queries—it can contain more if the query uses the `@defer` directive or subscriptions.
13. The router service receives the `SupergraphResponse` and serializes the GraphQL responses to JSON.
14. The HTTP server sends the JSON in an HTTP response to the client.

## Request and response nuances

For simplicity's sake, the preceding diagrams show the request and response sides separately and sequentially. In reality, some requests and responses may happen simultaneously and repeatedly.

For example, `SubgraphRequest`s can happen both in parallel _and_ in sequence: one subgraph's response may be necessary for another's `SubgraphRequest`. (The query planner decides which requests can happen in parallel vs. which need to happen in sequence). To match subgraph requests to responses in customizations, the router exposes a `subgraph_request_id` field that will hold the same value in paired requests and responses.

### Requests run in parallel

```mermaid
flowchart LR;
subgraph parallel[" "]
subgraph executionService["Execution Service"]
executionPlugins[[Execution plugins]];
end

subgraph subgraphService["Subgraph Services"]
subgraph service1["Subgraph Service A"]
subgraphPlugins1[[Subgraph plugins]];
end
subgraph service2["Subgraph Service B"]
subgraphPlugins2[[Subgraph plugins]];
end
end


executionService --"1A. <code>SubgraphRequest</code>"--> service1;
executionService --"1B. <code>SubgraphRequest</code>"--> service2;
service1 --"4A. <code>SubgraphResponse</code>"--> executionService;
service2 --"4B. <code>SubgraphResponse</code>"--> executionService;
end
subgraphA[Subgraph A];
subgraphB[Subgraph B];

service1 --"2A. HTTP request"--> subgraphA;
service2 --"2B. HTTP request"--> subgraphB;
subgraphA --"3A. HTTP response"--> service1;
subgraphB --"3B. HTTP response"--> service2;
```

### Requests run sequentially

```mermaid
flowchart LR;
subgraph sequentially[" "]
subgraph executionService["Execution Service"]
executionPlugins[[Execution plugins]];
end

subgraph subgraphService["Subgraph Services"]
subgraph service1["Subgraph Service A"]
subgraphPlugins1[[Subgraph plugins]];
end
subgraph service2["Subgraph Service B"]
subgraphPlugins2[[Subgraph plugins]];
end
end


executionService --"1. <code>SubgraphRequest</code>"--> service1;
service1 --"4. <code>SubgraphResponse</code>"--> executionService;
executionService --"5. <code>SubgraphRequest</code>"--> service2;
service2 --"8. <code>SubgraphResponse</code>"--> executionService;
end
subgraphA[Subgraph A];
subgraphB[Subgraph B];

service1 --"2. HTTP request"--> subgraphA;
service2 --"6. HTTP request"--> subgraphB;
subgraphA --"3. HTTP response"--> service1;
subgraphB --"7. HTTP response"--> service2;
```

Additionally, some requests and responses may happen multiple times for the same operation. With subscriptions, for example, a subgraph sends a new `SubgraphResponse` whenever data is updated. Each response object travels through all the services in the response path and interacts with any customizations you've created.

### Request and Response buffering

The router expects to execute on a stream of data. In order to work correctly and provide high performance, the following expectations must be met:

* **Request Path**: No buffering before the end of the `router_service` processing step
* **Response Path**: No buffering

> In general, it's best to avoid buffering where possible. If necessary, it is ok to do so on the request path once the `router_service` step is complete.

This guidance applies if you are:
- Modifying the router
- Creating a native Rust plugin
- Creating a custom binary

## Instrumenting and observing the request lifecycle

To understand the state and health of your router as it services requests, you can add instrumentation in the Router, Supergraph, and Subgraph services and export the collected data to your APM or metrics backend.

Supported instruments include:

* Standard OpenTelemetry instrumentation
* Standard router request lifecycle instruments
* Custom instruments

## Customizing the request lifecycle

Each service can have a set of customizable plugins that can be executed before or after the service:

- For requests, the router executes plugins _before_ the service.

```mermaid
flowchart LR
subgraph Service
Plugin1["Plugin 1"] -->|request| Plugin2["Plugin 2"] -->|request| coreService["Core <br/>service"]
coreService
end

Client -->|request| Plugin1
coreService -->|request| NextService["Next service"]
```

- For responses, the router executes the plugins _after_ the service.

```mermaid
flowchart RL
subgraph Service
coreService["Core <br/>service"] -->|response| Plugin2["Plugin 2"] -->|response| Plugin1["Plugin 1"]
end

Plugin1["Plugin 1"] -->|response| Client
NextService["Next service"] -->|response| coreService
```

Each request and response object contains a `Context` object, which is carried throughout the entire process. Each request's `Context` object is unique. You can use it to store plugin-specific information between the request and response or to communicate between different hook points. (A plugin can be called at multiple steps of the request lifecycle.)

To learn how to hook in to the various lifecycle stages, including examples customizations, refer to the [Rhai scripts](/graphos/routing/customization/rhai/) and [external coprocessing](/router/customizations/coprocessor/) docs.
Loading