Router Request Lifecycle
Understand how the router processes client requests
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.
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:
The router receives a client request at an HTTP server.
The HTTP server transforms the HTTP request into a
RouterRequest
containing HTTP headers and the request body as a stream of byte arrays.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 resultingSupergraphRequest
.The supergraph service calls the query planner with the GraphQL query from the
SupergraphRequest
.The query planner returns a query plan for most efficiently executing the query.
The supergraph service calls the execution service with an
ExecutionRequest
, made up ofSupergraphRequest
and the query plan.For each fetch node of the query plan, the execution service creates a
SubgraphRequest
and then calls the respective subgraph service.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. TheSubgraphRequest
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:
Each subgraph provides an HTTP response to the subgraph services.
Each subgraph service creates a
SubgraphResponse
containing the HTTP headers and a GraphQL response.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
.The
SupergraphResponse
has the same content as theExecutionResponse
. 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.The router service receives the
SupergraphResponse
and serializes the GraphQL responses to JSON.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 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
Requests run sequentially
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, 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 to capture data points along the request lifecycle. To customize events, you can set conditions to control when events are triggered, and attributes and selectors to specify the data attached to events.
To learn more about router observability with telemetry, go to Router 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.
For responses, the router executes the plugins after the service.
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, then refer to the Rhai scripts and external coprocessing docs.