ใช้ pg-Promise Tasks กับ graphql และ dataloader

Sep 22 2020

สิ่งแรกคือต้องบอกว่าฉันได้เห็นคำถามนี้การแบ่งปันงานสัญญา pg ในส่วนต่างๆของคำขอ httpและอ่านคำตอบ ฉันต้องการทบทวนคำถามที่มีความซับซ้อนเพิ่มเติม

ฉันกำลังทำงานบนเซิร์ฟเวอร์ typescript graphql apollo-server-expressโดยใช้ฐานข้อมูล postgres เป็นแบ็กเอนด์และpg-promiseเป็นไลบรารีอินเทอร์เฟซฐานข้อมูล ฉันมีตัวแก้ไขการสืบค้นโดยใช้ dataloaders โดยใช้รูปแบบที่อธิบายไว้ที่นี่: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 DanielRearden Sep 22 2020 at 10:02

ความจริงที่ว่าคุณกำลังใช้ DataLoader อยู่ที่นี่ส่วนใหญ่ไม่เกี่ยวข้อง เหมือนที่คุณแนะนำแล้วคุณก็ต้องผ่านสิ่งเช่นคุณกำลังใช้เรียกวิธีการในการquery createLoadersหากคุณไม่ได้ใช้ DataLoader คุณจะยังคงมีคำถามเดิม ๆ นั่นคือเราจะสร้างงานและทำให้มันพร้อมใช้งานสำหรับผู้แก้ไขทั้งหมดของเราได้อย่างไร

เท่าที่ฉันทราบวิธีเดียวที่จะทำได้กับ Apollo Server คือการสร้างปลั๊กอินที่กำหนดเอง คุณสามารถตรวจสอบส่วนสำคัญนี้ซึ่งแสดงให้เห็นถึงหลักการเดียวกันยกเว้นการทำธุรกรรม อย่างมีประสิทธิภาพคุณจะทำสิ่งที่ชอบ:

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
            }
          })
      }
    }
  }
})

ติดตั้งปลั๊กอินของคุณตามที่แสดงในเอกสาร :

const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [
    createTaskPlugin(db),
  ],
})

จากนั้นภายในตัวแก้ไขของคุณคุณจะต้องทำสิ่งต่อไปนี้:

resolve (parent, args, context) {
  return runWithTask(t => t.query('SELECT * FROM table_name'))
}