|
| 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. |
0 commit comments