async-graphql/ARCHITECTURE.md

2.3 KiB

async-graphql Architecture

This document describes the internal architecture of async-graphql, and can be useful to people wanting to contribute.

Schema

When you create a schema, the first thing it does it asks the query, mutation and subscription types to register themselves in the schema's list of GraphQL types called the registry. Those types will then recursively register all the types that they depend on in the registry, and so on until every single type that is used has been registered in the registry.

Query Execution

First of all, async-graphql will use the async-graphql-parser crate (located in the parser/ directory) to parse the request document source. This also performs some necessary validations such as making sure that operation (i.e. query/mutation/subscription) names are unique and that the query does not contain an anonymous operation as well as a named one.

It then will validate the document as per the rest of GraphQL's validation rules. The validation/ module handles this, and it works by walking through the entire document once and notifying the validation rules on the way to perform their validations and report errors if necessary. If ValidationMode::Fast is turned on, far far fewer rules are used.

Also at this stage some non-validators use the same architecture, such as the query depth calculator which keeps track of how deeply nested the query gets as the document is walked through.

At this point all the unnecessary operations (ones not selected by operationName) are dropped, and we will execute just one.

At the core of all the resolver logic there are two traits: InputType and OutputType which represent a GraphQL input value and GraphQL output value respectively. InputType just requires conversions to and from async_graphql::Value. OutputType is an async trait with a single method, resolve, which takes a field (e.g. user(name: "sunli829") { display_name }) and resolves it to a single value.

Scalars and enums are expected to ignore the input and serialize themselves, while objects, interfaces and unions are expected to read the selection set in the field and resolve and serialize each one of their fields.

As implementing OutputType::resolve manually quickly becomes very tedious helpful utilities are provided in the resolver_utils module and via macros.