将 pg-promise 任务与 graphql 和 dataloader 一起使用

Posted

技术标签:

【中文标题】将 pg-promise 任务与 graphql 和 dataloader 一起使用【英文标题】:Using pg-promise Tasks with graphql and dataloader 【发布时间】:2021-01-08 02:31:33 【问题描述】:

首先要说我看到了这个问题Sharing a pg-promise task across parts of an http request并阅读了答案。我想重新审视这个问题,增加一些复杂性。

我正在使用 postgres 数据库作为后端,pg-promise 作为数据库接口库,在 apollo-server-express 上开发 typescript graphql 服务器。我有使用数据加载器的查询解析器,使用此处描述的模式: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 来共享 dataloaders 对象,但如前所述,任务中的所有调用都发生在一个回调函数,因此通过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 你的建议和链接的要点最终奏效了。如果总是打开任务成为问题,我可能会检查来自插件内的传入请求。标记你的答案正确!

【问题讨论】:

【参考方案1】:

您在此处使用 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'))

【讨论】:

以上是关于将 pg-promise 任务与 graphql 和 dataloader 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

Pg-promise插入/事务在异步队列中不起作用

在 REST API 中使用 GraphQL 进行数据查询

pg-promise 将整数作为字符串返回

将嵌套循环查询组合到父数组结果 - pg-promise

我应该在哪里初始化 pg-promise

pg-promise:返回查询结果的正确方法