misc(doc): add extension documentation

This commit is contained in:
Miaxos 2021-11-02 20:15:43 +00:00
parent ecc6810aa5
commit a880617906
4 changed files with 223 additions and 1 deletions

View File

@ -9,6 +9,7 @@
- [Error handling](error_handling.md)
- [Merging Objects / Subscriptions](merging_objects.md)
- [Derived fields](derived_fields.md)
- [Complex Object](define_complex_object.md)
- [Enum](define_enum.md)
- [Interface](define_interface.md)
- [Union](define_union.md)
@ -27,6 +28,9 @@
- [Apollo Tracing](apollo_tracing.md)
- [Query complexity and depth](depth_and_complexity.md)
- [Hide content in introspection](visibility.md)
- [Extensions](extensions.md)
- [How extensions are working](extensions_inner_working.md)
- [Available extensions](extensions_available.md)
- [Integrations](integrations.md)
- [Tide](integrations_to_tide.md)
- [Warp](integrations_to_warp.md)
@ -34,5 +38,4 @@
- [Advanced topics](advanced_topics.md)
- [Custom scalars](custom_scalars.md)
- [Optimizing N+1 queries](dataloader.md)
- [Custom extensions](custom_extensions.md)
- [Apollo Federation](apollo_federation.md)

View File

@ -0,0 +1,3 @@
# Extensions
`async-graphql` has the capability to be extended with extensions without having to modify the original source code. A lot of features can be added this way, and a lot of extensions already exists.

View File

@ -0,0 +1,54 @@
# Extensions available
There are a lot of available extensions in the `async-graphql` to empower your GraphQL Server, some of these documentations are documented here.
## Analyzer
*Available in the repository*
The `analyzer` extension will output a field containing `complexity` and `depth` in the response extension field of each query.
## Apollo Persisted Queries
*Available in the repository*
To improve network performance for large queries, you can enable this Persisted Queries extension. With this extension enabled, each unique query is associated to a unique identifier, so clients can send this identifier instead of the corresponding query string to reduce requests sizes.
This extension doesn't force you to use some cache strategy, you can choose the caching strategy you want, you'll just have to implement the `CacheStorage` trait:
```rust
#[async_trait::async_trait]
pub trait CacheStorage: Send + Sync + Clone + 'static {
/// Load the query by `key`.
async fn get(&self, key: String) -> Option<String>;
/// Save the query by `key`.
async fn set(&self, key: String, query: String);
}
```
### References
[Apollo doc - Persisted Queries](https://www.apollographql.com/docs/react/api/link/persisted-queries/)
## Apollo Tracing
*Available in the repository*
Apollo Tracing is an extension which includes analytics data for your queries. This extension works to follow the old and now deprecated [Apollo Tracing Spec](https://github.com/apollographql/apollo-tracing). If you want to check the newer Apollo Reporting Protocol, it's implemented by [async-graphql Apollo studio extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension) for Apollo Studio.
## Apollo Studio
*Available at [async-graphql/async_graphql_apollo_studio_extension](https://github.com/async-graphql/async_graphql_apollo_studio_extension)*
Apollo Studio is a cloud platform that helps you build, validate, and secure your organization's graph (description from the official documentation). It's a service allowing you to monitor & work with your team around your GraphQL Schema. `async-graphql` provides an extension implementing the official [Apollo Specification](https://www.apollographql.com/docs/studio/setup-analytics/#third-party-support) available at [async-graphql-extension-apollo-tracing](https://github.com/async-graphql/async_graphql_apollo_studio_extension) and [Crates.io](https://crates.io/crates/async-graphql-extension-apollo-tracing).
## Logger
*Available in the repository*
Logger is a simple extension allowing you to add some logging feature to `async-graphql`. It's also a good example to learn how to create your own extension.
## OpenTelemetry
*Available in the repository*
OpenTelemetry is an extension providing an integration with the [opentelemetry crate](https://crates.io/crates/opentelemetry) to allow your application to capture distributed traces and metrics from `async-grraphql`.
## Tracing
*Available in the repository*
Tracing is a simple extension allowing you to add some tracing feature to `async-graphql`. A little like the `Logger` extension.

View File

@ -0,0 +1,162 @@
# How extensions are defined
An `async-graphql` extension is defined by implementing the trait `Extension` associated. The `Extension` trait allow you to insert custom code to some several steps used to respond to GraphQL's queries through `async-graphql`. With `Extensions`, your application can hook into the GraphQL's requests lifecycle to add behaviors about incoming requests or outgoing response.
`Extensions` are a lot like middleware from other frameworks, be careful when using those: when you use an extension **it'll be run for every GraphQL requests**.
Across every step, you'll have the `ExtensionContext` supplied with data about your current request execution. Feel free to check how it's constructed in the code, documentation about it will soon come.
## A word about middleware
For those who don't know, let's dig deeper into what is a middleware:
```rust
async fn middleware(&self, ctx: &ExtensionContext<'_>, next: NextMiddleware<'_>) -> MiddlewareResult {
// Logic to your middleware.
/*
* Final step to your middleware, we call the next function which will trigger
* the execution of the next middleware. It's like a `callback` in JavaScript.
*/
next.run(ctx).await
}
```
As you have seen, a `Middleware` is only a function calling the next function at the end, but we could also do a middleware with the `next` function at the start. This is where it's becoming tricky: depending on where you put your logics and where is the `next` call, your logic won't have the same execution order.
Depending on your logic code, you'll want to process it before or after the `next` call. If you need more information about middlewares, there are a lot of things in the web.
## Processing of a query
There are several steps to go to process a query to completion, you'll be able to create extension based on these hooks.
### request
First, when we receive a request, if it's not a subscription, the first function to be called will be `request`, it's the first step, it's the function called at the incoming request, and it's also the function which will output the response to the user.
Default implementation for `request`:
```rust
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
next.run(ctx).await
}
```
Depending on where you put your logic code, it'll be executed at the beginning or at the ending of the query being processed.
```rust
async fn request(&self, ctx: &ExtensionContext<'_>, next: NextRequest<'_>) -> Response {
// The code here will be run before the prepare_request is executed.
let result = next.run(ctx).await;
// The code after the completion of this futue will be after the processing, just before sending the result to the user.
result
}
```
### prepare_request
Just after the `request`, we will have the `prepare_request` lifecycle, which will be hooked.
```rust
async fn prepare_request(
&self,
ctx: &ExtensionContext<'_>,
request: Request,
next: NextPrepareRequest<'_>,
) -> ServerResult<Request> {
// The code here will be un before the prepare_request is executed, just after the request lifecycle hook.
let result = next.run(ctx, request).await;
// The code here will be run just after the prepare_request
result
}
```
### parse_query
The `parse_query` will create a GraphQL `ExecutableDocument` on your query, it'll check if the query is valid for the GraphQL Spec. Usually the implemented spec in `async-graphql` tends to be the last stable one (October2021).
```rust
/// Called at parse query.
async fn parse_query(
&self,
ctx: &ExtensionContext<'_>,
// The raw query
query: &str,
// The variables
variables: &Variables,
next: NextParseQuery<'_>,
) -> ServerResult<ExecutableDocument> {
next.run(ctx, query, variables).await
}
```
### validation
The `validation` step will check (depending on your `validation_mode`) rules the query should abide to and give the client data about why the query is not valid.
```rust
/// Called at validation query.
async fn validation(
&self,
ctx: &ExtensionContext<'_>,
next: NextValidation<'_>,
) -> Result<ValidationResult, Vec<ServerError>> {
next.run(ctx).await
}
```
### execute
The `execution` step is a huge one, it'll start the execution of the query by calling each resolver concurrently for a `Query` and serially for a `Mutation`.
```rust
/// Called at execute query.
async fn execute(
&self,
ctx: &ExtensionContext<'_>,
operation_name: Option<&str>,
next: NextExecute<'_>,
) -> Response {
// Befoe starting resolving the whole query
let result = next.run(ctx, operation_name).await;
// After resolving the whole query
result
}
````
### resolve
The `resolve` step is launched for each field.
```rust
/// Called at resolve field.
async fn resolve(
&self,
ctx: &ExtensionContext<'_>,
info: ResolveInfo<'_>,
next: NextResolve<'_>,
) -> ServerResult<Option<Value>> {
// Logic before resolving the field
let result = next.run(ctx, info).await;
// Logic after resolving the field
result
}
```
### subscribe
The `subscribe` lifecycle has the same behavior as the `request` but for a `Subscritpion`.
```rust
/// Called at subscribe request.
fn subscribe<'s>(
&self,
ctx: &ExtensionContext<'_>,
stream: BoxStream<'s, Response>,
next: NextSubscribe<'_>,
) -> BoxStream<'s, Response> {
next.run(ctx, stream)
}
```