Utilisation des tâches pg-promise avec graphql et dataloader
La première chose est de dire que j'ai vu cette question Partager une tâche pg-promise entre les parties d'une requête http et lire la réponse. J'aimerais revenir sur la question avec une complication supplémentaire.
Je travaille sur un serveur graphql dactylographié sur l' apollo-server-expressutilisation d'une base de données postgres comme backend et pg-promisecomme bibliothèque d'interface de base de données. J'ai des résolveurs de requêtes utilisant des chargeurs de données utilisant le modèle décrit ici:https://github.com/graphql/dataloader#creating-a-new-dataloader-per-request.
Tous les accès à la base de données se font via une seule instance de pg-promisecomme recommandé, par exemplehttps://github.com/vitaly-t/pg-promise-demo/blob/master/TypeScript/db/index.ts.
Comme la question originale, j'essaie de trouver un bon moyen de créer une tâche qui peut être incorporée dans les chargeurs de données (ou passée comme paramètre lorsque le chargeur de données est appelé) de sorte que les résolveurs de requêtes fonctionnent dans une seule connexion de base de données. Je ne sais pas quels résolveurs seront appelés ni dans quel ordre ils seront appelés, donc je ne peux pas dire qu'un certain résolveur devrait créer la tâche.
J'ai essayé de trouver un moyen d'utiliser le graphql Contextpour partager la tâche car j'utilise actuellement le Contextpour partager l'objet dataloaders, mais comme cela a été noté, tous les appels de la tâche se produisent dans une fonction de rappel, donc le partage de la tâche via le Contextest en dehors. Voici un exemple de configuration du contexte:
/*
createLoaders() returns a new object of DataLoaders e.g.
{
users: new DataLoader(ids => getUsers(ids)),
tags: new DataLoader(ids => getTags(ids))
}
*/
import { createLoaders } from './dataloaders'
const server = new ApolloServer({
schema,
context: (): Promise<Context> => {
// create a connection here to inject into createLoaders?
const loaders = createLoaders()
return { loaders }
}
})
et un exemple dans la documentation Apollo de la création d'une connexion db dans le contexte: https://www.apollographql.com/docs/apollo-server/data/resolvers/#the-context-argument (notez qu'ils ne décrivent pas la fermeture de la connexion n'importe où).
context: async () => ({
db: await client.connect(),
})
// Resolver
(parent, args, context, info) => {
return context.db.query('SELECT * FROM table_name');
}
Cela peut ne pas être possible sans utiliser la connectméthode, mais son utilisation pour ce type d'opération est déconseillée par l'auteur de la bibliothèque. Et le même problème se pose: la connexion doit être renvoyée au pool lorsque la requête est finalement résolue juste avant de renvoyer les résultats au client.
Toute aide est appréciée! (et merci à @ vitaly-t pour un si bon travail sur pg-promise)
Edit 1 @ Daniel-Rearden votre suggestion et l'essentiel lié ont fini par fonctionner. J'inspecterai probablement les demandes entrantes depuis le plugin si toujours ouvrir une tâche devient un problème. Marquer votre réponse correcte!
Réponses
Le fait que vous utilisiez DataLoader ici est en grande partie hors de propos. Comme vous l'avez déjà suggéré, il vous suffit de transmettre l'instance que vous utilisez pour appeler la queryméthode createLoaders. Si vous n'utilisiez pas DataLoader, vous vous retrouveriez toujours avec la même question, à savoir comment créer une tâche et la rendre disponible à tous nos résolveurs.
Pour autant que je sache, la seule façon de faire cela avec Apollo Server est de créer un plugin personnalisé. Vous pouvez consulter cet essentiel qui a démontré le même principe, sauf pour les transactions. En fait, vous feriez quelque chose comme:
import { ApolloServerPlugin, GraphQLRequestContext } from 'apollo-server-plugin-base'
export const createTaskPlugin = (db: Database): ApolloServerPlugin => ({
requestDidStart(requestContext: GraphQLRequestContext) {
let taskResult: Promise<{ ok?: boolean, error?: any }> | void = undefined
return {
executionDidStart() {
let ok: (value?: unknown) => void
let fail: (reason?: any) => void
const didFinish = new Promise((resolve, reject) => {
ok = resolve
fail = reject
})
const task = new Promise((resolve) =>
taskResult = db.task(t => {
resolve(t)
return didFinish
}).then(() => ({ ok: true }), error => ({ error }))
)
requestContext.context.runWithTask = async (cb) => cb(await task)
return (err) => {
if (err) fail(err)
ok()
}
},
willSendResponse() {
if (!taskResult) return
return taskResult
.then(({ error }) => {
if (error) {
throw error
}
})
}
}
}
})
Installez votre plugin comme indiqué ici dans la documentation :
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
createTaskPlugin(db),
],
})
Ensuite, dans votre résolveur, vous feriez quelque chose comme:
resolve (parent, args, context) {
return runWithTask(t => t.query('SELECT * FROM table_name'))
}