#Fluent
You're viewing documentation for a previous version of this software. Switch to the latest stable version
Fluent is the most common choice of connecting to a database from a Vapor application. There can be some confusion on how to connect Fluent entities into a GraphQL Schema, so here are some information to help tackle any of those issue.
#GraphQL ID
Let's use Graphiti as the GraphQL schema library and we have a User
fluent entity as described below.
Describing this class and most of its properties in Graphiti should be simple enough. However as you can see here, UUID is its struct and not a primitive in the GraphQL spec.
On the other hand, Pioneer already have provided a struct for the GraphQL ID
primitive. We can take advantage of Swift extensions and computed properties to describe the entity's UUID
into Pioneer's ID
.
From that, we can use the new computed properties in the schema instead of using the id
property.
#Fluent Relationship
#Relationship Resolver
Say we have a new struct Item
that have a many to one relationship to User
. You can easily describe this into the GraphQL schema with using Swift's extension.
Using extensions, we can describe a custom resolver function to fetch the User
for the Item
.
#Resolver on Item
N+1 problem
In a real producation application, this example resolver is flawed with the N+1 problem.
And update the schema accordingly.
This approach is actually not a specific to Pioneer. You can use the same or similar solutions if you are using Vapor, Fluent, and Graphiti, albeit without some features provided by Pioneer (i.e. async await resolver, and custom ID struct).
#N+1 Problem
Imagine your graph has query that lists items
with the items
resolver looked like
and the Item
has relationship resolver looked like Item.owner
.
The graph will executed that Resolver.items
function which will make a request to the database to get all items.
Furthermore for each item, the graph will also execute the Item.owner
function which make another request to the databse to get the user with the given id. Resulting in the following SQL statements:
What's worse is that certain items can be owned by the same user so these statements will likely query for the same users multiple times.
This is what's called the N+1 problem which you want to avoid. The solution? DataLoader.
#DataLoader
The GraphQL Foundation provided a specification for solution to the N+1 problem called dataloader
. Essentially, dataloaders combine the fetching of process across all resolvers for a given GraphQL request into a single query.
DataLoader with async-await
Newest version of DataLoader already provide extensions to use it with async await.
However if you are using older version of DataLoader, Pioneer also provide extensions to use DataLoader with async await since v0.5.2
.
The package Dataloader implement that solution for GraphQLSwift/GraphQL.
After that, we can create a function to build a new dataloader for each Request
, and update the relationship resolver to use the loader
Loading Many
In cases where you have an arrays of ids of users and need to fetch those users in a relationship resolver, Dataloader have a method called loadMany
which takes multiple keys and return them all.
In other cases where you have the user id but need to fetch all items with that user id, you can just have the loader be DataLoader<UUID, [Item]>
where the UUID
is the user id and now load
should return an array of Item
.
Now instead of having n+1 queries to the database by using the dataloader, the only SQL queries sent to the database are:
which is significantly better.
#EagerLoader
Fluent provides a way to eagerly load relationship which will solve the N+1 problem by joining the SQL statement.
However, it forces you fetch the relationship regardless whether it is requested in the GraphQL operation which can be considered overfetching.
Whether it is a better option is up to you and your use cases, but do keep in mind that GraphQL promotes the avoidance of overfetching.