ใช้ pg-Promise Tasks กับ graphql และ dataloader
สิ่งแรกคือต้องบอกว่าฉันได้เห็นคำถามนี้การแบ่งปันงานสัญญา 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 ข้อเสนอแนะและส่วนสำคัญที่เชื่อมโยงของคุณสิ้นสุดการทำงาน ฉันมักจะตรวจสอบคำขอที่เข้ามาจากภายในปลั๊กอินหากการเปิดงานมักจะกลายเป็นปัญหา ทำเครื่องหมายคำตอบของคุณถูกต้อง!
คำตอบ
ความจริงที่ว่าคุณกำลังใช้ 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'))
}