-
Notifications
You must be signed in to change notification settings - Fork 275
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: router request lifecycle (#6497)
- Loading branch information
Showing
8 changed files
with
337 additions
and
267 deletions.
There are no files selected for viewing
29 changes: 29 additions & 0 deletions
29
docs/shared/diagrams/router-request-lifecycle-overview.mdx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
```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 | ||
client -->|request| router -->|request| infra | ||
infra -->|response| router -->|response| client | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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** - Operates within the context of an HTTP server, handling the opaque bytes of an incoming HTTP request. Does query analysis to parse the GraphQL operation and validate it against schema. | ||
* **Supergraph service** - Handles a GraphQL request after it's been parsed and validated, and before it's 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
--- | ||
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. | ||
--- | ||
|
||
import RequestLifecycleOverviewDiagram from '../../shared/diagrams/router-request-lifecycle-overview.mdx'; | ||
|
||
Every client request made to a GraphOS Router goes through the **router request lifecycle**: a multi-stage pipeline of services that processes requests and returns responses. | ||
|
||
<RequestLifecycleOverviewDiagram /> | ||
|
||
<Tip> | ||
|
||
Understanding the router request lifecycle makes it easier for you to write router customization plugins or configure router telemetry. | ||
|
||
</Tip> | ||
|
||
The router processes a client request by first passing it between services along the lifecycle's **request path**. The request path breaks up a request and sends constituent sub-requests to subgraphs. Then along the lifecycle's **response path**, the router gathers all the responses from the subgraph requests into a client response. | ||
|
||
## Request path | ||
|
||
In the request path, the request lifecycle services process each request 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. | ||
|
||
Each service encapsulates and transforms the contents of a request into its own context. The following diagram and its steps describe how an HTTP request is transformed and propagated through the request path: | ||
|
||
```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, validates the query against the schema, 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 | ||
|
||
Subgraph responses follow the response path. | ||
|
||
## Response path | ||
|
||
In the response path, the lifecycle services gather subgraph responses into a client response 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 | ||
|
||
Although the preceding diagrams showed the request and response paths 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](/graphos/reference/federation/query-plans) 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. | ||
|
||
## Observability of the request lifecycle | ||
|
||
To understand the state and health of your router as it services requests, you can add instrumentation to request lifecycle services and collect telemetry. The router's telemetry is based on [OpenTelemetry](https://opentelemetry.io/docs/what-is-opentelemetry/), so you can configure your router's YAML configuration to add traces, metrics, and logs. | ||
|
||
You can instrument the Router, Supergraph, and Subgraph services with [events](/router/configuration/telemetry/instrumentation/events) to capture data points along the request lifecycle. To customize events, you can set [conditions](/router/configuration/telemetry/instrumentation/conditions) to control when events are triggered, and [attributes](/router/configuration/telemetry/instrumentation/events#attributes) and [selectors](/router/configuration/telemetry/instrumentation/selectors) to specify the data attached to events. | ||
|
||
To learn more about router observability with telemetry, go to [Router Telemetry](/graphos/routing/observability/telemetry). | ||
|
||
## Router customizations along the request lifecycle | ||
|
||
You can create customizations for the router to extend its functionality. Customizations intervene at specific points of the request lifecycle, where each point is represented by a specific service with its own request and response objects. | ||
|
||
Customizations are implemented as plugins. Each service of the request lifecycle 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, start with the [router customization overview](/graphos/routing/customization/overview), then refer to the [Rhai scripts](/graphos/routing/customization/rhai/) and [external coprocessing](/router/customizations/coprocessor/) docs. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters