misc(doc): add extension documentation
This commit is contained in:
parent
ecc6810aa5
commit
a880617906
|
@ -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)
|
||||
|
|
|
@ -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.
|
|
@ -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.
|
|
@ -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)
|
||||
}
|
||||
```
|
Loading…
Reference in New Issue