การจัดการลำดับการ resolution ของฟิลด์
เป้าหมายของ directive @export ที่ Multiple Query Execution มอบให้คือการ export ค่าของฟิลด์ (หรือชุดฟิลด์) ไปยังตัวแปร เพื่อนำไปใช้ในที่อื่นในการ query
directive นี้จะไม่ทำงานหากการอ่านตัวแปรเกิดขึ้นก่อนการ export ค่าไปยังตัวแปร ดังนั้นเอนจินจึงต้องมีวิธีควบคุมลำดับการประมวลผลฟิลด์
Gato GraphQL มีวิธีจัดการลำดับการประมวลผลฟิลด์ผ่านการ query เอง เอนจินโหลดข้อมูลเป็นรอบ ๆ สำหรับแต่ละ type โดยแก้ไขฟิลด์ทั้งหมดจาก type แรกที่พบใน query ก่อน จากนั้นจึงแก้ไขฟิลด์ทั้งหมดจาก type ที่สองที่พบใน query และดำเนินการต่อเช่นนี้จนกว่าจะไม่มี type ให้ประมวลผล
ตัวอย่างเช่น query ต่อไปนี้ที่เกี่ยวข้องกับออบเจ็กต์ของ type Director, Film และ Actor:
{
directors {
name
films {
title
actors {
name
}
}
}
}...จะถูกแก้ไขโดย GraphQL engine ตามลำดับนี้:

หากหลังจากประมวลผลแล้ว type ถูกอ้างอิงอีกครั้งใน query เพื่อดึงข้อมูลที่ยังไม่ได้โหลด (เช่น จากออบเจ็กต์เพิ่มเติม หรือฟิลด์เพิ่มเติมจากออบเจ็กต์ที่โหลดไว้แล้ว) type นั้นจะถูกเพิ่มอีกครั้งที่ส่วนท้ายของรายการรอบ
ตัวอย่างเช่น หากเรายัง query ฟิลด์ preferredDirector ของ Actor (ซึ่งส่งคืนออบเจ็กต์ของ type Director) เช่นนี้:
{
directors {
name
films {
title
actors {
name
preferredDirector {
name
}
}
}
}
}...GraphQL engine จะประมวลผล query ตามลำดับนี้:

มาดูกันว่าสิ่งนี้จะเกิดขึ้นอย่างไรสำหรับการรัน @export ใน query เดียว สำหรับความพยายามแรก เราสร้าง query ตามปกติโดยไม่คำนึงถึงลำดับการประมวลผลของฟิลด์:
query GetPostsAuthorNames {
user(by: { id: 1 }) {
name @export(as: "authorName")
}
posts(filter: { search: $authorName }) {
id
title
}
}เมื่อรัน query จะได้รับ response นี้:

...ซึ่งมีข้อผิดพลาดต่อไปนี้:
{
"errors": [
{
"message": "Expression 'authorName' is undefined",
}
]
}ข้อผิดพลาดนี้หมายความว่า ในขณะที่ตัวแปร $authorName ถูกอ่าน มันยังไม่ได้ถูกกำหนดค่า นั่นคือมีค่าเป็น undefined
มาดูว่าเหตุใดสิ่งนี้จึงเกิดขึ้น ก่อนอื่น เราวิเคราะห์ว่า type ใดบ้างที่ปรากฏใน query โดยเพิ่มเป็น comment ด้านล่าง:
# Type: Root
query GetPostsAuthorNames {
# Type: User
user(by: {id: 1}) {
# Type: String
name @export(as: "authorName")
}
# Type: Post
posts(filter: { search: $authorName }) {
# Type: ID
id
# Type: String
title
}
}เพื่อประมวลผล type และโหลดข้อมูล data-loading engine จะเพิ่ม query type Root ลงในรายการ FIFO (First-In, First-Out) ทำให้ [Root] เป็นรายการเริ่มต้นที่ส่งไปยังอัลกอริทึม จากนั้น iterate ผ่าน type ตามลำดับดังนี้:
| # | การดำเนินการ | รายการ |
|---|---|---|
| 0 | เตรียมรายการ FIFO | [Root] |
| 1a | นำ type แรกในรายการออก (Root) | [] |
| 1b | ประมวลผลฟิลด์ทั้งหมดที่ query จาก type Root:→ user(by: {id: 1})→ posts(filter: { search: $authorName })เพิ่ม type ของพวกเขา ( User และ Post) ลงในรายการ | [User, Post] |
| 2a | นำ type แรกในรายการออก (User) | [Post] |
| 2b | ประมวลผลฟิลด์ที่ query จาก type User:→ name @export(as: "authorName")เนื่องจากเป็น scalar type ( String) จึงไม่จำเป็นต้องเพิ่มลงในรายการ | [Post] |
| 3a | นำ type แรกในรายการออก (Post) | [] |
| 3b | ประมวลผลฟิลด์ทั้งหมดที่ query จาก type Post:→ id→ titleเนื่องจากเป็น scalar type ( ID และ String) จึงไม่จำเป็นต้องเพิ่มลงในรายการ | [] |
| 4 | รายการว่างเปล่า การ iterate สิ้นสุด |
ที่นี่เราเห็นปัญหา: @export ถูกประมวลผลในขั้นตอน 2b แต่ถูกอ่านในขั้นตอน 1b
ที่นี่เองที่เราต้องควบคุมการไหลของการประมวลผลฟิลด์ วิธีแก้ที่นำมาใช้คือการหน่วงเวลาการอ่านตัวแปรที่ export ไว้ โดยทำได้ด้วยการ query ฟิลด์ self จาก type Root อย่างจงใจ
ฟิลด์ self ตามชื่อที่บ่งบอก จะส่งคืนออบเจ็กต์เดิม เมื่อนำไปใช้กับออบเจ็กต์ Root จะส่งคืนออบเจ็กต์ Root เดิม คุณอาจสงสัยว่า "ถ้าฉันมีออบเจ็กต์ root อยู่แล้ว ทำไมต้องดึงมันมาอีกครั้ง?" เพราะอัลกอริทึมของเอนจินจำเป็นต้องเพิ่มการอ้างอิงใหม่ไปยัง Root นี้ที่ส่วนท้ายของรายการ FIFO และเราสามารถกระจายฟิลด์ที่ query ไว้ก่อนหรือหลังแต่ละ iteration เหล่านี้ได้อย่างจงใจ
นั่นคือเหตุผลที่ฟิลด์ posts(filter:{ search: $authorName }) ถูกวางไว้ภายในฟิลด์ self ใน query ด้านบน และการรัน query จะได้รับ response ที่คาดหวัง:
query GetPostsAuthorNames {
user(by: {id: 1}) {
name @export(as: "authorName")
}
self {
posts(filter: { search: $authorName }) {
id
title
}
}
}
มาสำรวจลำดับการประมวลผล type ใน query นี้เพื่อทำความเข้าใจว่าทำไมมันจึงทำงานได้ดี:
| # | การดำเนินการ | รายการ |
|---|---|---|
| 0 | เตรียมรายการ FIFO | [Root] |
| 1a | นำ type แรกในรายการออก (Root) | [] |
| 1b | ประมวลผลฟิลด์ทั้งหมดที่ query จาก type Root:→ user(by: {id: 1})→ selfเพิ่ม type ของพวกเขา ( User และ Root) ลงในรายการ | [User, Root] |
| 2a | นำ type แรกในรายการออก (User) | [Root] |
| 2b | ประมวลผลฟิลด์ที่ query จาก type User:→ name @export(as: "authorName")เนื่องจากเป็น scalar type ( String) จึงไม่จำเป็นต้องเพิ่มลงในรายการ | [Root] |
| 3a | นำ type แรกในรายการออก (Root) | [] |
| 3b | ประมวลผลฟิลด์ที่ query จาก type Root:→ posts(filter:{ search: $authorName })เพิ่ม type ของมัน ( Post) ลงในรายการ | [Post] |
| 4a | นำ type แรกในรายการออก (Post) | [] |
| 4b | ประมวลผลฟิลด์ทั้งหมดที่ query จาก type Post:→ id→ titleเนื่องจากเป็น scalar type ( ID และ String) จึงไม่จำเป็นต้องเพิ่มลงในรายการ | [] |
| 5 | รายการว่างเปล่า การ iterate สิ้นสุด |
ตอนนี้เราเห็นแล้วว่าปัญหาได้รับการแก้ไขแล้ว: @export ถูกประมวลผลในขั้นตอน 2b และถูกอ่านในขั้นตอน 3b
Multiple Query Execution ทำสิ่งนี้ทุกครั้งเมื่อ แยก queries: มันแปลง GraphQL document โดยเพิ่มฟิลด์ self เพื่อให้ฟิลด์ในแต่ละ operation ถูกประมวลผลเฉพาะหลังจากที่ฟิลด์ทั้งหมดในทุก operation ก่อนหน้าได้รับการแก้ไขแล้ว