Utilizzo di pg-promise Tasks con graphql e dataloader
La prima cosa è dire che ho visto questa domanda Condivisione di un'attività pg-promise tra parti di una richiesta http e ho letto la risposta. Vorrei rivisitare la domanda con qualche complicazione aggiuntiva.
Sto lavorando su un server graphql dattiloscritto apollo-server-expresssull'utilizzo di un database postgres come backend e pg-promisecome libreria dell'interfaccia del database. Ho risolutori di query che utilizzano dataloader utilizzando il modello descritto qui:https://github.com/graphql/dataloader#creating-a-new-dataloader-per-request.
Tutti gli accessi al database avvengono tramite una singola istanza di pg-promisecome raccomandato ad eshttps://github.com/vitaly-t/pg-promise-demo/blob/master/TypeScript/db/index.ts.
Come la domanda originale, sto cercando di trovare un buon modo per creare un'attività che può essere incorporata nei dataloader (o passato come parametro quando viene chiamato il dataloader) in modo tale che i risolutori di query operino all'interno di una singola connessione al database. Non so quali resolver verranno chiamati o in quale ordine verranno chiamati, quindi non posso dire che un determinato resolver debba creare l'attività.
Ho cercato di trovare un modo per utilizzare il graphql Contextper condividere l'attività poiché attualmente sto usando Contextper condividere l'oggetto dataloader ma, come è stato notato, tutte le chiamate nell'attività avvengono all'interno di una funzione di callback, quindi la condivisione dell'attività tramite Contextis su. Ecco un esempio di come è impostato il contesto:
/*
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 }
}
})
e un esempio nei documenti Apollo di creazione di una connessione db all'interno del contesto: https://www.apollographql.com/docs/apollo-server/data/resolvers/#the-context-argument (nota che non descrivono la chiusura della connessione da nessuna parte).
context: async () => ({
db: await client.connect(),
})
// Resolver
(parent, args, context, info) => {
return context.db.query('SELECT * FROM table_name');
}
Ciò potrebbe non essere possibile senza utilizzare il connectmetodo, ma il suo utilizzo per questo tipo di operazione è sconsigliato dall'autore della libreria. E si verifica lo stesso problema che la connessione deve essere restituita al pool quando la query viene finalmente risolta appena prima di inviare i risultati al client.
Qualsiasi aiuto è apprezzato! (e grazie a @ vitaly-t per l'ottimo lavoro svolto pg-promise)
Modifica 1 @ Daniel-Rearden il tuo suggerimento e la sintesi collegata hanno funzionato. Probabilmente ispezionerò le richieste in arrivo dall'interno del plug-in se aprire sempre un'attività diventa un problema. Contrassegnare la risposta corretta!
Risposte
Il fatto che tu stia utilizzando DataLoader qui è in gran parte irrilevante. Come hai già suggerito, devi solo passare l'istanza che stai utilizzando per chiamare il querymetodo createLoaders. Se non stavi usando DataLoader, ti rimarrebbe comunque la stessa domanda, ovvero come creiamo un'attività e la rendiamo disponibile a tutti i nostri risolutori.
Per quanto ne so, l'unico modo per farlo con Apollo Server è creare un plug-in personalizzato. Puoi dare un'occhiata a questa sintesi che ha dimostrato lo stesso principio tranne che con le transazioni. Effettivamente, faresti qualcosa come:
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
}
})
}
}
}
})
Installa il tuo plugin come mostrato qui nei documenti :
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
createTaskPlugin(db),
],
})
Quindi all'interno del tuo resolver, faresti qualcosa del tipo:
resolve (parent, args, context) {
return runWithTask(t => t.query('SELECT * FROM table_name'))
}