# Fluent

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.

User.swift
import Foundation
import Vapor
import Fluent

final class User: Model, Content {
    static var schema: String = "users"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "name")
    var name: String

    init() { }

    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}

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.

User+Graphiti.swift
import Foundation
import Pioneer

extension User {
    var gid: ID {
        id?.toID() ?? .uuid()
    }
}

From that, we can use the new computed properties in the schema instead of using the id property.

Schema.swift
import Foundation
import Graphiti
import Pioneer

func schema() throws -> Schema<Resolver, Context> {
    try .init {
        ID.asScalar()

        Type(User.self) {
            Field("id", at: \.gid)
            Field("name", at: \.name)
        }

        ...
    }
}

# 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.

Item.swift
import Foundation
import Vapor
import Fluent

final class Item: Model, Content {
    static let schema = "items"

    @ID(key: .id)
    var id: UUID?

    @Field(key: "name")
    var name: String

    @Parent(key: "user_id")
    var user: User

    init() { }

    init(name: String, userID: User.IDValue) {
        self.name = name
        self.$user.id = userID
    }
}

Using extensions, we can describe a custom resolver function to fetch the User for the Item.

# Resolver on Item
Item+GraphQL.swift
import Foundation
import Fluent
import Vapor
import Pioneer
import Graphiti

extension Item {
    func owner(ctx: Context, _: NoArguments) async throws -> User? {
        return try await User.find($user.id, on: ctx.req.db)
    }
}

And update the schema accordingly.

Schema.swift
import Foundation
import Graphiti
import Pioneer

func schema() throws -> Schema<Resolver, Context> {
    try .init {
        ID.asScalar()

        Type(User.self) {
            Field("id", at: \.gid)
            Field("name", at: \.name)
        }

        Type(Item.self) {
            Field("name", at: \.name)
            Field("owner", at: Item.owner, as: TypeReference<User>.self)
        }

        ...
    }
}

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

query {
  items {
    name
    owner {
      id
      name
    }
  }
}

with the items resolver looked like

Resolver.swift
struct Resolver {
    func items(ctx: Context, _: NoArguments) async throws -> [Item] {
        try await Item.query(on: ctx.req.d).all()
    }
}

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:

N+1 queries
SELECT * FROM items
SELECT * FROM users WHERE id = ?
SELECT * FROM users WHERE id = ?
SELECT * FROM users WHERE id = ?
SELECT * FROM users WHERE id = ?
SELECT * FROM users WHERE id = ?
...

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.

The package Dataloader implement that solution for GraphQLSwift/GraphQL.

Adding DataLoader
.package(url: "https://github.com/GraphQLSwift/DataLoader", from: "...")

After that, we can create a function to build a new dataloader for each Request, and update the relationship resolver to use the loader

Loader and Context
struct Context {
    ...
    // Loader computed on each Context or each request
    var userLoader: DataLoader<UUID, User>
}

extension User {
    func makeLoader(req: Request) -> DataLoader<UUID, User> {
        .init(on: req) { keys async in
            let users = try? await User.query(on: req.db).filter(\.$id ~~ keys).all()
            return keys.map { key in
                guard let user = res?.first(where: { $0.id == key }) else {
                    return .error(GraphQLError(
                        message: "No user with corresponding key: \(key)"
                    ))
                }
                return .success(user)
            }
        }
    }
}
Item+GraphQL.swift
extension Item {
    func owner(ctx: Context, _: NoArguments, ev: EventLoopGroup) async throws -> User? {
        guard let uid = $user.id else {
            return nil
        }
        return try await ctx.userLoader.load(key: uid, on: ev.next())
    }
}

Now instead of having n+1 queries to the database by using the dataloader, the only SQL queries sent to the database are:

SELECT * FROM items
SELECT * FROM users WHERE id IN (?, ?, ?, ?, ?, ...)

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.

Resolver.swift
struct Resolver {
    func items(ctx: Context, _: NoArguments) async throws -> [Item] {
        try await Item.query(on: ctx.req.d).with(\.$user).all()
    }
}
Item+GraphQL.swift
extension Item {
    func owner(_: Context, _: NoArguments) async -> User? {
        return $user
    }
}

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.