将 pg-promise 任务与 graphql 和数据加载器一起使用
Using pg-promise Tasks with graphql and dataloader
首先要说的是,我已经看到了这个问题并阅读了答案。我想重新审视这个问题,增加一些复杂性。
我正在 apollo-server-express
上开发 typescript graphql 服务器,使用 postgres 数据库作为后端,pg-promise
作为数据库接口库。我有使用此处描述的模式使用数据加载器的查询解析器:https://github.com/graphql/dataloader#creating-a-new-dataloader-per-request.
所有数据库访问都按照推荐的 pg-promise
的单个实例进行,例如https://github.com/vitaly-t/pg-promise-demo/blob/master/TypeScript/db/index.ts.
与最初的问题一样,我正在尝试找到一种创建任务的好方法,该任务可以合并到数据加载器中(或在调用数据加载器时作为参数传递),以便查询解析器在单个数据库中运行联系。我不知道将调用哪些解析器或调用它们的顺序,所以我不能说某个解析器应该创建任务。
我一直在想办法使用 graphql Context
来共享任务,因为我目前正在使用 Context
来共享数据加载器对象,但如前所述,所有调用在任务中发生在回调函数中,因此通过 Context
共享任务已经结束。这是上下文设置方式的示例:
/*
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 }
}
})
以及在上下文中创建数据库连接的 Apollo 文档中的示例:https://www.apollographql.com/docs/apollo-server/data/resolvers/#the-context-argument(请注意,它们没有描述在任何地方关闭连接)。
context: async () => ({
db: await client.connect(),
})
// Resolver
(parent, args, context, info) => {
return context.db.query('SELECT * FROM table_name');
}
如果不使用 connect
方法,这可能是不可能的,但是库的作者不鼓励将其用于此类操作。同样的问题是,在将结果发送回客户端之前最终解决查询时,需要将连接返回到池中。
感谢任何帮助! (感谢@vitaly-t 在 pg-promise
上的出色工作)
编辑 1
@Daniel-Rearden 你的建议和链接的要点最终起作用了。如果总是打开任务成为一个问题,我可能会检查来自插件内部的传入请求。标记您的答案正确!
您在这里使用 DataLoader 这一事实在很大程度上是无关紧要的。正如您已经建议的那样,您只需将用于调用 query
方法的任何实例传递给 createLoaders
。如果您不使用 DataLoader,您仍然会遇到同样的问题,即我们如何创建一个任务并使其可供我们所有的解析器使用。
据我所知,使用 Apollo Server 做到这一点的唯一方法是创建一个自定义插件。您可以查看 this gist,它展示了相同的原理,但交易除外。实际上,你会做类似的事情:
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
}
})
}
}
}
})
如图所示安装插件 here in the docs:
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
createTaskPlugin(db),
],
})
然后在你的解析器中,你会做类似的事情:
resolve (parent, args, context) {
return runWithTask(t => t.query('SELECT * FROM table_name'))
}
首先要说的是,我已经看到了这个问题
我正在 apollo-server-express
上开发 typescript graphql 服务器,使用 postgres 数据库作为后端,pg-promise
作为数据库接口库。我有使用此处描述的模式使用数据加载器的查询解析器:https://github.com/graphql/dataloader#creating-a-new-dataloader-per-request.
所有数据库访问都按照推荐的 pg-promise
的单个实例进行,例如https://github.com/vitaly-t/pg-promise-demo/blob/master/TypeScript/db/index.ts.
与最初的问题一样,我正在尝试找到一种创建任务的好方法,该任务可以合并到数据加载器中(或在调用数据加载器时作为参数传递),以便查询解析器在单个数据库中运行联系。我不知道将调用哪些解析器或调用它们的顺序,所以我不能说某个解析器应该创建任务。
我一直在想办法使用 graphql Context
来共享任务,因为我目前正在使用 Context
来共享数据加载器对象,但如前所述,所有调用在任务中发生在回调函数中,因此通过 Context
共享任务已经结束。这是上下文设置方式的示例:
/*
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 }
}
})
以及在上下文中创建数据库连接的 Apollo 文档中的示例:https://www.apollographql.com/docs/apollo-server/data/resolvers/#the-context-argument(请注意,它们没有描述在任何地方关闭连接)。
context: async () => ({
db: await client.connect(),
})
// Resolver
(parent, args, context, info) => {
return context.db.query('SELECT * FROM table_name');
}
如果不使用 connect
方法,这可能是不可能的,但是库的作者不鼓励将其用于此类操作。同样的问题是,在将结果发送回客户端之前最终解决查询时,需要将连接返回到池中。
感谢任何帮助! (感谢@vitaly-t 在 pg-promise
上的出色工作)
编辑 1 @Daniel-Rearden 你的建议和链接的要点最终起作用了。如果总是打开任务成为一个问题,我可能会检查来自插件内部的传入请求。标记您的答案正确!
您在这里使用 DataLoader 这一事实在很大程度上是无关紧要的。正如您已经建议的那样,您只需将用于调用 query
方法的任何实例传递给 createLoaders
。如果您不使用 DataLoader,您仍然会遇到同样的问题,即我们如何创建一个任务并使其可供我们所有的解析器使用。
据我所知,使用 Apollo Server 做到这一点的唯一方法是创建一个自定义插件。您可以查看 this gist,它展示了相同的原理,但交易除外。实际上,你会做类似的事情:
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
}
})
}
}
}
})
如图所示安装插件 here in the docs:
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
createTaskPlugin(db),
],
})
然后在你的解析器中,你会做类似的事情:
resolve (parent, args, context) {
return runWithTask(t => t.query('SELECT * FROM table_name'))
}