Skip to content

Commit a4bbf5d

Browse files
authored
docs: router request lifecycle (#6497)
2 parents 273ee25 + aef8571 commit a4bbf5d

File tree

8 files changed

+337
-267
lines changed

8 files changed

+337
-267
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
```mermaid
2+
flowchart RL
3+
subgraph client["Client"]
4+
end
5+
6+
subgraph router["Router"]
7+
direction LR
8+
routerService("Router <br/> Service")
9+
supergraphService("Supergraph <br/> Service")
10+
executionService("Execution <br/> Service")
11+
subgraphService("Subgraph <br/> Service")
12+
routerService -->|request| supergraphService -->|request| executionService -->|request| subgraphService
13+
subgraphService -->|response| executionService -->|response| supergraphService -->|response| routerService
14+
15+
end
16+
17+
subgraph infra["Your infrastructure"]
18+
direction TB
19+
api1("subgraph A");
20+
api2("subgraph B");
21+
api3("subgraph C");
22+
api1 --- api2 --- api3
23+
24+
end
25+
26+
client -->|request| router -->|request| infra
27+
28+
infra -->|response| router -->|response| client
29+
```
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
A router's request lifecycle has three major services:
1+
A router's request lifecycle has three major services that support instrumentation:
22

3-
* **Router service** - Handles an incoming request before it is parsed. Works within a context of opaque bytes.
4-
* **Supergraph service** - Handles a request after it has been parsed and before it is sent to the subgraph. Works within a GraphQL context.
5-
* **Subgraph service** - Handles a request after it has been sent to the subgraph. Works within a GraphQL context.
3+
* **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.
4+
* **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.
5+
* **Subgraph service** - Handles GraphQL subgraph requests that have been executed as part of a query plan. Creates HTTP client requests to subgraphs.
6+
7+
<Note>
8+
9+
The router's **Execution service** that executes query plans doesn't support instrumentation.
10+
11+
</Note>

docs/source/reference/router/telemetry/instrumentation/events.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ description: Capture standard and custom events from the Apollo GraphOS Router's
77
import RouterServices from '../../../../../shared/router-lifecycle-services.mdx';
88
import TelemetryPerformanceNote from '../../../../../shared/telemetry-performance.mdx';
99

10-
An _event_ is used to signal when something of note happens in the [GraphOS Router's request lifecycle](/router/customizations/overview/#the-request-lifecycle). Events are output to both logs and traces.
10+
An _event_ is used to signal when something of note happens in the [GraphOS Router's request lifecycle](/graphos/routing/request-lifecycle). Events are output to both logs and traces.
1111

1212
You can configure events for each service in `router.yaml`. Events can be standard or custom, and they can be triggered by configurable conditions.
1313

docs/source/reference/router/telemetry/instrumentation/spans.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ description: Use spans to add contextual information from the Apollo GraphOS Rou
66

77
import RouterServices from '../../../../../shared/router-lifecycle-services.mdx';
88

9-
A **span** captures contextual information about requests and responses as they're processed through the [router's request lifecycle (pipeline)](/router/customizations/overview/#the-request-lifecycle). The information from spans can be used when displaying traces in your application performance monitors (APM).
9+
A **span** captures contextual information about requests and responses as they're processed through the [router's request lifecycle (pipeline)](/graphos/routing/request-lifecycle). The information from spans can be used when displaying traces in your application performance monitors (APM).
1010

1111
## Spans configuration
1212

docs/source/routing/customization/overview.mdx

Lines changed: 24 additions & 259 deletions
Large diffs are not rendered by default.

docs/source/routing/observability/telemetry.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ flowchart LR
2626

2727
## Observability through telemetry
2828

29-
The health of your supergraph is only as good as the health of your router. Because the router is the single entry point to the supergraph, all client requests pass through the [router's request lifecycle](/router/customizations/overview#the-request-lifecycle). Any issues with the router are likely to affect the handling of all requests to your supergraph.
29+
The health of your supergraph is only as good as the health of your router. Because the router is the single entry point to the supergraph, all client requests pass through the [router request lifecycle](/graphos/routing/request-lifecycle). Any issues with the router are likely to affect the handling of all requests to your supergraph.
3030

3131
Diagnosing your router's health and performance requires it to show observable data about its inner workings. The more observable data you can monitor and analyze, the faster you can identify unhealthy behaviors, deduce root causes, and implement fixes.
3232

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
title: Router Request Lifecycle
3+
subtitle: Understand how the router processes client requests
4+
description: Understand how GraphQL client requests get processed by the request lifecycle pipeline of Apollo GraphOS Router.
5+
---
6+
7+
import RequestLifecycleOverviewDiagram from '../../shared/diagrams/router-request-lifecycle-overview.mdx';
8+
9+
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.
10+
11+
<RequestLifecycleOverviewDiagram />
12+
13+
<Tip>
14+
15+
Understanding the router request lifecycle makes it easier for you to write router customization plugins or configure router telemetry.
16+
17+
</Tip>
18+
19+
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.
20+
21+
## Request path
22+
23+
In the request path, the request lifecycle services process each request in the following order:
24+
25+
* The **Router service** receives the client request from the HTTP server and parses it into a GraphQL operation.
26+
* 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.
27+
* The **Execution service** executes a query plan by calling the necessary subgraph services to make subgraph requests
28+
* Each subgraph has an associated **Subgraph service** that makes HTTP requests to the subgraph.
29+
30+
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:
31+
32+
```mermaid
33+
flowchart TB;
34+
client(Client);
35+
subgraph router["Router"]
36+
direction LR
37+
httpServer("HTTP server")
38+
subgraph routerService["Router Service"]
39+
routerPlugins[[Router plugins]];
40+
end
41+
subgraph " "
42+
subgraph supergraphService["Supergraph Service"]
43+
supergraphPlugins[[Supergraph plugins]];
44+
end
45+
queryPlanner("Query Planner");
46+
end
47+
48+
49+
subgraph executionService["Execution Service"]
50+
executionPlugins[[Execution plugins]];
51+
end
52+
53+
subgraph subgraphService["Subgraph Services"]
54+
subgraph service1["Subgraph Service A"]
55+
subgraphPlugins1[[Subgraph plugins]];
56+
end
57+
subgraph service2["Subgraph Service B"]
58+
subgraphPlugins2[[Subgraph plugins]];
59+
end
60+
end
61+
end;
62+
subgraphA[Subgraph A];
63+
subgraphB[Subgraph B];
64+
65+
client --"1. HTTP request"--> httpServer;
66+
httpServer --"2. <code>RouterRequest</code>"--> routerService;
67+
routerService --"3. <code>SupergraphRequest</code>"--> supergraphService
68+
supergraphService --"4. Query"--> queryPlanner;
69+
queryPlanner --"5. Query plan"--> supergraphService;
70+
supergraphService --"6. <code>ExecutionRequest</code>"--> executionService;
71+
72+
executionService --"7a. <code>SubgraphRequest</code>"--> service1;
73+
executionService --"7b. <code>SubgraphRequest</code>"--> service2;
74+
75+
service1 --"8a. HTTP request"--> subgraphA;
76+
service2 --"8b. HTTP request"--> subgraphB;
77+
```
78+
79+
1. The router receives a client request at an HTTP server.
80+
2. The HTTP server transforms the HTTP request into a `RouterRequest` containing HTTP headers and the request body as a stream of byte arrays.
81+
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`.
82+
4. The supergraph service calls the query planner with the GraphQL query from the `SupergraphRequest`.
83+
5. The query planner returns a query plan for most efficiently executing the query.
84+
6. The supergraph service calls the execution service with an `ExecutionRequest`, made up of `SupergraphRequest` and the query plan.
85+
7. For each fetch node of the query plan, the execution service creates a `SubgraphRequest` and then calls the respective subgraph service.
86+
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:
87+
- the (read-only) `SupergraphRequest`
88+
- HTTP headers
89+
- the subgraph request's operation type (query, mutation, or subscription)
90+
- a GraphQL request object as the request body
91+
92+
Subgraph responses follow the response path.
93+
94+
## Response path
95+
96+
In the response path, the lifecycle services gather subgraph responses into a client response in the following order:
97+
98+
* The **Execution service** receives and formats all subgraph responses.
99+
* The **Supergraph service** gathers the content of all subgraph responses into stream.
100+
* The **Router service** serializes the stream of responses into JSON and forwards it to the HTTP server to send it to the client.
101+
102+
The following diagram and its steps describe the response path in further detail:
103+
104+
```mermaid
105+
flowchart BT;
106+
client(Client);
107+
subgraph " "
108+
direction LR
109+
httpServer("HTTP server")
110+
subgraph routerService["Router Service"]
111+
routerPlugins[[Router plugins]];
112+
end
113+
subgraph " "
114+
subgraph supergraphService["Supergraph Service"]
115+
supergraphPlugins[[Supergraph plugins]];
116+
end
117+
queryPlanner("QueryPlanner");
118+
end
119+
120+
121+
subgraph executionService["Execution Service"]
122+
executionPlugins[[Execution plugins]];
123+
end
124+
125+
subgraph subgraphService["Subgraph Services"]
126+
subgraph service1["Subgraph Service A"]
127+
subgraphPlugins1[[Subgraph plugins]];
128+
end
129+
subgraph service2["Subgraph Service B"]
130+
subgraphPlugins2[[Subgraph plugins]];
131+
end
132+
end
133+
end;
134+
subgraph1[Subgraph A];
135+
subgraph2[Subgraph B];
136+
137+
subgraph1 -- "9a. HTTP response"--> service1;
138+
subgraph2 -- "9b. HTTP response"--> service2;
139+
service1 --"10a. <code>SubgraphResponse</code>"--> executionService;
140+
service2 --"10b. <code>SubgraphResponse</code>"--> executionService;
141+
executionService --"11. <code>ExecutionResponse</code>"--> supergraphService;
142+
supergraphService --"12. <code>SupergraphResponse</code>"--> routerService;
143+
routerService --"13. <code>RouterResponse</code>"--> httpServer;
144+
httpServer --"14. HTTP response" --> client
145+
```
146+
9. Each subgraph provides an HTTP response to the subgraph services.
147+
10. Each subgraph service creates a `SubgraphResponse` containing the HTTP headers and a GraphQL response.
148+
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`.
149+
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.
150+
13. The router service receives the `SupergraphResponse` and serializes the GraphQL responses to JSON.
151+
14. The HTTP server sends the JSON in an HTTP response to the client.
152+
153+
## Request and response nuances
154+
155+
Although the preceding diagrams showed the request and response paths separately and sequentially, in reality some requests and responses may happen simultaneously and repeatedly.
156+
157+
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.
158+
159+
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.
160+
161+
### Requests run in parallel
162+
163+
```mermaid
164+
flowchart LR;
165+
subgraph parallel[" "]
166+
subgraph executionService["Execution Service"]
167+
executionPlugins[[Execution plugins]];
168+
end
169+
170+
subgraph subgraphService["Subgraph Services"]
171+
subgraph service1["Subgraph Service A"]
172+
subgraphPlugins1[[Subgraph plugins]];
173+
end
174+
subgraph service2["Subgraph Service B"]
175+
subgraphPlugins2[[Subgraph plugins]];
176+
end
177+
end
178+
179+
180+
executionService --"1A. <code>SubgraphRequest</code>"--> service1;
181+
executionService --"1B. <code>SubgraphRequest</code>"--> service2;
182+
service1 --"4A. <code>SubgraphResponse</code>"--> executionService;
183+
service2 --"4B. <code>SubgraphResponse</code>"--> executionService;
184+
end
185+
subgraphA[Subgraph A];
186+
subgraphB[Subgraph B];
187+
188+
service1 --"2A. HTTP request"--> subgraphA;
189+
service2 --"2B. HTTP request"--> subgraphB;
190+
subgraphA --"3A. HTTP response"--> service1;
191+
subgraphB --"3B. HTTP response"--> service2;
192+
```
193+
194+
### Requests run sequentially
195+
196+
```mermaid
197+
flowchart LR;
198+
subgraph sequentially[" "]
199+
subgraph executionService["Execution Service"]
200+
executionPlugins[[Execution plugins]];
201+
end
202+
203+
subgraph subgraphService["Subgraph Services"]
204+
subgraph service1["Subgraph Service A"]
205+
subgraphPlugins1[[Subgraph plugins]];
206+
end
207+
subgraph service2["Subgraph Service B"]
208+
subgraphPlugins2[[Subgraph plugins]];
209+
end
210+
end
211+
212+
213+
executionService --"1. <code>SubgraphRequest</code>"--> service1;
214+
service1 --"4. <code>SubgraphResponse</code>"--> executionService;
215+
executionService --"5. <code>SubgraphRequest</code>"--> service2;
216+
service2 --"8. <code>SubgraphResponse</code>"--> executionService;
217+
end
218+
subgraphA[Subgraph A];
219+
subgraphB[Subgraph B];
220+
221+
service1 --"2. HTTP request"--> subgraphA;
222+
service2 --"6. HTTP request"--> subgraphB;
223+
subgraphA --"3. HTTP response"--> service1;
224+
subgraphB --"7. HTTP response"--> service2;
225+
```
226+
227+
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.
228+
229+
## Observability of the request lifecycle
230+
231+
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.
232+
233+
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.
234+
235+
To learn more about router observability with telemetry, go to [Router Telemetry](/graphos/routing/observability/telemetry).
236+
237+
## Router customizations along the request lifecycle
238+
239+
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.
240+
241+
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:
242+
243+
- For requests, the router executes plugins _before_ the service.
244+
245+
```mermaid
246+
flowchart LR
247+
subgraph Service
248+
Plugin1["Plugin 1"] -->|request| Plugin2["Plugin 2"] -->|request| coreService["Core <br/>service"]
249+
coreService
250+
end
251+
252+
Client -->|request| Plugin1
253+
coreService -->|request| NextService["Next service"]
254+
```
255+
256+
- For responses, the router executes the plugins _after_ the service.
257+
258+
```mermaid
259+
flowchart RL
260+
subgraph Service
261+
coreService["Core <br/>service"] -->|response| Plugin2["Plugin 2"] -->|response| Plugin1["Plugin 1"]
262+
end
263+
264+
Plugin1["Plugin 1"] -->|response| Client
265+
NextService["Next service"] -->|response| coreService
266+
```
267+
268+
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.
269+
270+
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.

docs/source/routing/security/authorization.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ Using the `@policy` directive requires a [Supergraph plugin](/router/customizati
489489

490490
An overview of how `@policy` is processed through the router's request lifecycle:
491491

492-
- At the [`RouterService` level](/router/customizations/overview#the-request-lifecycle), the GraphOS Router extracts the list of policies relevant to a request from the schema and then stores them in the request's context in `apollo::authorization::required_policies` as a map `policy -> null|true|false`.
492+
- At the [`RouterService` level](/graphos/routing/request-lifecycle), the GraphOS Router extracts the list of policies relevant to a request from the schema and then stores them in the request's context in `apollo::authorization::required_policies` as a map `policy -> null|true|false`.
493493

494494
- At the `SupergraphService` level, you must provide a Rhai script or coprocessor to evaluate the map.
495495
If the policy is validated, the script or coprocessor should set its value to `true` or otherwise set it to `false`. If the value is left to `null`, it will be treated as `false` by the router. Afterward, the router filters the requests' types and fields to only those where the policy is `true`.

0 commit comments

Comments
 (0)