# Relationship

Relationship are integral part of GraphQL. It define how one entity integrate with another.

# Relationship Resolver

Relationship field (fields that are referencing other types) can be done using a relationship resolver, which is similar to any field resolver.

Say we have a type Car that have many Parts, where each Part holds the id for the Car it is for.

import struct Pioneer.ID

struct Car: Identifiable {
    var id: ID
    var model: String
}

struct Part: Identifiable {
    var id: ID
    var name: String
    var carId: Car.ID
}

# Resolver on Object type

Using extensions, we can describe a custom resolver function to fetch the Car for a given Part, and getting all the Part for a given Car.

extension Car {
    func parts(ctx: Context, _: NoArguments) async throws -> [Part] {
        try await ctx.db.find(Part.self).filter { $0.carId == id }
    }
}

extension Part {
    func car(ctx: Context, _: NoArguments) async throws -> User? {
        try await ctx.db.find(Car.self).first { $0.id == carId }
    }
}

And update the schema accordingly.

type Car {
  id: ID!
  model: String!
  parts: [Part!]!
}

type Part {
  id: ID!
  name: String!
  car: Car
}
import Graphiti
import Pioneer

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

        Type(Car.self) {
            Field("id", at: \.id)
            Field("model", at: \.model)
            Field("parts", at: Car.parts, at: [Part].self)
        }

        Type(Part.self) {
            Field("id", at: \.id)
            Field("name", at: \.name)
            Field("car", at: Part.car, as: Car?.self)
        }
    }
}

# N+1 Problem

Imagine your graph has query that lists items

query {
  parts {
    name
    car {
      id
      model
    }
  }
}

with the parts resolver looked like

Resolver.swift
struct Resolver {
    func parts(ctx: Context, _: NoArguments) async throws -> [Part] {
        try await ctx.db.find(Part.self)
    }
}

The graph will executed that Resolver.parts function which will make a request to the database to get all items.

Let's assume the database is a SQL database and the following SQL statements created when resolving the query are:

SELECT * FROM parts
SELECT * FROM cars WHERE id = ?
SELECT * FROM cars WHERE id = ?
SELECT * FROM cars WHERE id = ?
SELECT * FROM cars WHERE id = ?
SELECT * FROM cars WHERE id = ?
...

What's worse is that certain parts can be for the same car 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.

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

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

struct Context {
    var db: Database
    var carLoader: DataLoader<Car.ID, Car?>
    var partsLoader: DataLoader<Car.ID, [Part]>
}

extension Car {
    func loader(ev: EventLoop, db: Database) -> DataLoader<Car.ID, Car?> {
        .init(on: ev) { keys in
            let cars = try? await db.find(Car.self).filter { keys.contains($0.id) }
            return keys.map { key in
                guard let car = cars?.first(where: { $0.id == key }) else {
                    return .succes(nil)
                }
                return .success(car)
            }
        }
    }
}

extension Part {
   func loader(ev: EventLoop, db: Database) -> DataLoader<Car.ID, [Part]> {
        .init(on: ev) { keys in
            let all = try? await db.find(Part.self).filter { keys.contains($0.carId) }
            return keys.map { key in
                guard let parts = all?.filter({ $0.carId == key }) else {
                    return .success([])
                }
                return .success(parts)
            }
        }
    } 
}

# Using dataloader in resolvers

extension Car {
    func parts(ctx: Context, _: NoArguments) async throws -> [Part] {
        try await ctx.partsLoader.load(key: id, on: ev)
    }
}

extension Part {
    func car(ctx: Context, _: NoArguments, ev: EventLoopGroup) async throws -> User? {
        try await ctx.carLoader.load(key: carId, on: ev)
    }
}

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 parts
SELECT * FROM cars WHERE id IN (?, ?, ?, ?, ?, ...)

which is significantly better.