Skip to content

Commit

Permalink
docs: router request lifecycle (#6497)
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Feb 4, 2025
2 parents 273ee25 + aef8571 commit a4bbf5d
Show file tree
Hide file tree
Showing 8 changed files with 337 additions and 267 deletions.
29 changes: 29 additions & 0 deletions docs/shared/diagrams/router-request-lifecycle-overview.mdx
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
```
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** - 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>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Capture standard and custom events from the Apollo GraphOS Router's
import RouterServices from '../../../../../shared/router-lifecycle-services.mdx';
import TelemetryPerformanceNote from '../../../../../shared/telemetry-performance.mdx';

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.
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.

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Use spans to add contextual information from the Apollo GraphOS Rou

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

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).
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).

## Spans configuration

Expand Down
283 changes: 24 additions & 259 deletions docs/source/routing/customization/overview.mdx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/source/routing/observability/telemetry.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ flowchart LR

## Observability through telemetry

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.
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.

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.

Expand Down
270 changes: 270 additions & 0 deletions docs/source/routing/request-lifecycle.mdx
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.
2 changes: 1 addition & 1 deletion docs/source/routing/security/authorization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ Using the `@policy` directive requires a [Supergraph plugin](/router/customizati

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

- 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`.
- 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`.

- At the `SupergraphService` level, you must provide a Rhai script or coprocessor to evaluate the map.
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`.
Expand Down

0 comments on commit a4bbf5d

Please sign in to comment.