การรัน queries หลายรายการพร้อมกัน
สามารถรวม queries หลายรายการเข้าด้วยกันและรันเป็นการดำเนินการเดียว โดยนำ state และข้อมูลของแต่ละ query มาใช้ซ้ำได้
ฟีเจอร์นี้แตกต่างจาก query batching ที่ GraphQL server รันหลาย queries ในคำขอเดียวเช่นกัน แต่ queries เหล่านั้นจะถูกรันทีละรายการตามลำดับโดยไม่ขึ้นต่อกัน
ฟีเจอร์นี้ช่วยเพิ่มประสิทธิภาพ แทนที่จะรัน queries แยกกันในคำขอหลายรายการ (โดยส่งการดำเนินการไปยัง GraphQL server ก่อน รอรับผลลัพธ์ แล้วจึงนำผลนั้นไปใช้ในการดำเนินการถัดไป) เราสามารถรัน queries ทั้งหมดพร้อมกันได้ จึงหลีกเลี่ยง latency จากคำขอหลายรายการได้
Multiple Query Execution ยังช่วยให้เราจัดระเบียบ GraphQL queries ได้ดีขึ้น โดยแยกออกเป็นหน่วยย่อยที่มีตรรกะชัดเจน ซึ่งพึ่งพาซึ่งกันและกัน และรันตามเงื่อนไขจากผลลัพธ์ของการดำเนินการก่อนหน้า
วิธีใช้ multiple query execution
สมมติว่าเราต้องการค้นหาโพสต์ทั้งหมดที่กล่าวถึงชื่อของผู้ใช้ที่ล็อกอินอยู่ โดยปกติเราจะต้องใช้สอง queries เพื่อทำสิ่งนี้
ก่อนอื่นเราดึง name ของผู้ใช้:
query GetLoggedInUserName {
me {
name
}
}...จากนั้นหลังจากรัน query แรกแล้ว เราสามารถส่ง name ของผู้ใช้ที่ดึงมาเป็นตัวแปร $search เพื่อค้นหาใน query ที่สอง:
query GetPostsContainingString($search: String!) {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution ทำให้กระบวนการนี้ง่ายขึ้น โดยช่วยให้เราดึงข้อมูลทั้งหมดและรันตรรกะทั้งหมดที่ต้องการในคำขอเดียว:
query GetLoggedInUserName {
me {
name @export(as: "search")
}
}
query GetPostsContainingString @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $search }) {
id
title
}
}Multiple Query Execution ทำได้ด้วยการใช้ directives พิเศษเหล่านี้:
@depends(operation directive): ให้การดำเนินการ (ไม่ว่าจะเป็นqueryหรือmutation) ระบุว่าการดำเนินการอื่นใดต้องถูกรันก่อน@export(field directive): ส่งออกค่าของ field จากการดำเนินการหนึ่ง เพื่อนำไปใส่เป็น input ให้กับ field ในอีกการดำเนินการหนึ่ง@deferredExport(field directive): คล้ายกับ@exportแต่ใช้สำหรับ Multi-Field Directives
นอกจากนี้ directives @include และ @skip ยังสามารถใช้เป็น operation directives ได้ด้วย (ปกติเป็นเพียง field directives) และสามารถนำมาใช้เพื่อรันการดำเนินการตามเงื่อนไขหากตรงตามเงื่อนไขบางอย่าง
GraphQL server จะสร้างรายการการดำเนินการที่จะโหลดและรัน โดยดึงจากแต่ละ @depends(on: ...) และส่งออกค่าจาก field ที่มี @export เป็นตัวแปรแบบไดนามิก (โดยมีชื่อที่กำหนดภายใต้ argument as) เพื่อใช้เป็น input ในการดำเนินการถัดไป
การผสม directives เหล่านี้เข้าด้วยกัน เราสามารถแยกฟังก์ชันที่ซับซ้อนออกเป็นขั้นตอนย่อย สลับการดำเนินการ query และ mutation เพิ่มการพึ่งพาตามลำดับที่ต้องการ และรันทั้งหมดในคำขอเดียว โดยกำหนดการดำเนินการชั้นนอกสุดใน ?operationName=... (ในตัวอย่างข้างต้น คือ ?operationName=GetPostsContainingString)
การกำหนดการดำเนินการที่จะโหลดและรันผ่าน @depends
เมื่อ GraphQL document มีหลายการดำเนินการ เราระบุให้ server ทราบว่าจะรันอันไหนผ่าน URL param ?operationName=... มิฉะนั้นการดำเนินการสุดท้ายจะถูกรัน
เริ่มจากการดำเนินการเริ่มต้นนี้ server จะรวบรวมการดำเนินการทั้งหมดที่ต้องรัน ซึ่งกำหนดโดยการเพิ่ม directive depends(on: [...]) และรันตามลำดับที่สอดคล้องกับการพึ่งพา
Directive arg operations รับอาร์เรย์ของชื่อการดำเนินการ ([String]) หรือเราสามารถระบุชื่อการดำเนินการเดียว (String) ก็ได้
ใน query นี้เราส่ง ?operationName=Four และการดำเนินการที่รัน (ไม่ว่าจะเป็น query หรือ mutation) จะเป็น ["One", "Two", "Three", "Four"]:
mutation One {
# Do something ...
}
mutation Two {
# Do something ...
}
query Three @depends(on: ["One", "Two"]) {
# Do something ...
}
query Four @depends(on: "Three") {
# Do something ...
}การแชร์ข้อมูลระหว่าง queries ผ่าน @export
Directive @export ส่งออกค่าของ field (หรือชุดของ fields) ไปยังตัวแปรแบบไดนามิก เพื่อใช้เป็น input ใน field จาก query อื่น
ตัวอย่างเช่น ใน query นี้เราส่งออกชื่อของผู้ใช้ที่ล็อกอินอยู่ และใช้ค่านี้เพื่อค้นหาโพสต์ที่มีสตริงนั้น (โปรดสังเกตว่าตัวแปร $loggedInUserName เนื่องจากเป็นตัวแปรแบบไดนามิก จึงไม่จำเป็นต้องกำหนดใน operation FindPosts):
query GetLoggedInUserName {
me {
name @export(as: "loggedInUserName")
}
}
query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}ผลลัพธ์ของตัวแปรแบบไดนามิก
@export สามารถสร้างผลลัพธ์ได้ 6 แบบที่แตกต่างกัน โดยขึ้นอยู่กับการผสมผสานของ:
- ค่าของ argument
type(ไม่ว่าจะเป็นSINGLE,LISTหรือDICTIONARY) - ว่า directive ถูกใช้กับ field เดียวหรือหลาย fields (ผ่านโมดูล Multi-Field Directives)
6 ผลลัพธ์ที่เป็นไปได้คือ:
- ประเภท
SINGLE:- Single field
- Multi-field
- ประเภท
LIST:- Single field
- Multi-field
- ประเภท
DICTIONARY:- Single field
- Multi-field
ประเภท SINGLE / Single field
ผลลัพธ์เป็นค่าเดียวเมื่อส่ง param type: SINGLE (ซึ่งกำหนดเป็นค่าเริ่มต้น)
ใน query นี้:
query {
post(by: { id: 1 }) {
title @export(as: "postTitle", type: SINGLE)
}
}...ตัวแปรแบบไดนามิก $postTitle จะมีค่า:
"Hello world!"โปรดสังเกตว่าหาก SINGLE ถูกใช้กับอาร์เรย์ของ entities ค่าของ entity สุดท้ายจะเป็นค่าที่ถูกส่งออก
ใน query นี้:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitle", type: SINGLE)
}
}...ตัวแปรแบบไดนามิก $postTitle จะมีค่าของโพสต์ที่มี ID 5:
"Everything good?"ประเภท SINGLE / Multi-field
หาก @export ถูกใช้กับหลาย fields (โดยเพิ่ม param affectAdditionalFieldsUnderPos ที่จัดมาโดยโมดูล Multi-Field Directives) ค่าที่กำหนดให้กับตัวแปรแบบไดนามิกจะเป็น dictionary ของ { key: field alias, value: field value } (ประเภท JSONObject)
Query นี้:
query {
post(by: { id: 1 }) {
title
content
@export(
as: "postData",
type: SINGLE,
affectAdditionalFieldsUnderPos: [1]
)
}
}...ส่งออกตัวแปรแบบไดนามิก $postData ด้วยค่า:
{
"title": "Hello world!",
"content": "Lorem ipsum."
}ประเภท LIST / Single field
ตัวแปรแบบไดนามิกจะมีอาร์เรย์ที่มีค่าของ field จาก entities ทั้งหมดที่ query (จาก enclosing field) โดยส่ง param type: LIST
เมื่อรัน query นี้ (ซึ่ง entities ที่ query คือโพสต์ที่มี ID 1 และ 5):
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitles", type: LIST)
}
}...ตัวแปรแบบไดนามิก $postTitles จะมีค่า:
[
"Hello world!",
"Everything good?"
]ประเภท LIST / Multi-field
เราได้รับอาร์เรย์ของ dictionaries (ประเภท JSONObject) แต่ละรายการมีค่าของ fields ที่ directive ถูกใช้
Query นี้:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsData",
type: LIST,
affectAdditionalFieldsUnderPos: [1]
)
}
}...ส่งออกตัวแปรแบบไดนามิก $postsData ด้วยค่า:
[
{
"title": "Hello world!",
"content": "Lorem ipsum."
},
{
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
]ประเภท DICTIONARY / Single field
ตัวแปรแบบไดนามิกจะมี dictionary (ประเภท JSONObject) ที่มี ID ของ entity ที่ query เป็น key และค่าของ field เป็น value โดยส่ง param type: DICTIONARY
Query นี้:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postIDTitles", type: DICTIONARY)
}
}...ส่งออกตัวแปรแบบไดนามิก $postIDTitles ด้วยค่า:
{
"1": "Hello world!",
"5": "Everything good?"
}ประเภท DICTIONARY / Multi-field
ในการผสมผสานนี้ เราส่งออก dictionary ของ dictionaries: { key: entity ID, value: { key: field alias, value: field value } } (ใช้ประเภท JSONObject ที่มี entries ประเภท JSONObject)
Query นี้:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsIDProperties",
type: DICTIONARY,
affectAdditionalFieldsUnderPos: [1]
)
}
}...ส่งออกตัวแปรแบบไดนามิก $postsIDProperties ด้วยค่า:
{
"1": {
"title": "Hello world!",
"content": "Lorem ipsum."
},
"5": {
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
}การรันการดำเนินการตามเงื่อนไข
เมื่อ Multiple Query Execution เปิดใช้งาน directives @include และ @skip ก็สามารถใช้เป็น operation directives ได้ด้วย และสามารถนำมาใช้เพื่อรันการดำเนินการตามเงื่อนไขหากตรงตามเงื่อนไขบางอย่าง
ตัวอย่างเช่น ใน query นี้ operation CheckIfPostExists ส่งออกตัวแปรแบบไดนามิก $postExists และเฉพาะเมื่อค่าของมันเป็น true เท่านั้น mutation ExecuteOnlyIfPostExists จึงจะถูกรัน:
query CheckIfPostExists($id: ID!) {
# Initialize the dynamic variable to `false`
postExists: _echo(value: false) @export(as: "postExists")
post(by: { id: $id }) {
# Found the Post => Set dynamic variable to `true`
postExists: _echo(value: true) @export(as: "postExists")
}
}
mutation ExecuteOnlyIfPostExists
@depends(on: "CheckIfPostExists")
@include(if: $postExists)
{
# Do something...
}การส่งออกค่าเมื่อวนซ้ำอาร์เรย์หรือ JSON object
@export เคารพ cardinality จาก meta-directive ที่ครอบอยู่
โดยเฉพาะอย่างยิ่ง เมื่อ @export ถูกซ้อนอยู่ภายใต้ meta-directive ที่วนซ้ำ array items หรือ JSON object properties (กล่าวคือ @underEachArrayItem และ @underEachJSONObjectProperty) ค่าที่ส่งออกจะเป็นอาร์เรย์
Query นี้:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underEachArrayItem
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...สร้าง $contentAttributes ด้วยค่า:
[
"List Block",
"Columns Block",
"Columns inside Columns (nested inner blocks)",
"Life is so rich",
"Life is so dynamic"
]ในทางกลับกัน query เดียวกันที่เข้าถึง item เฉพาะในอาร์เรย์แทนที่จะวนซ้ำทั้งหมด (โดยแทนที่ @underEachArrayItem ด้วย @underArrayItem(index: 0)) จะส่งออกค่าเดียว
Query นี้:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underArrayItem(index: 0)
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...สร้าง $contentAttributes ด้วยค่า:
"List Block"ลำดับการรัน directive
หากมี directives อื่นก่อน @export ค่าที่ส่งออกจะสะท้อนการแก้ไขจาก directives ก่อนหน้าเหล่านั้น
ตัวอย่างเช่น ใน query นี้ ขึ้นอยู่กับว่า @export รันก่อนหรือหลัง @strUpperCase ผลลัพธ์จะแตกต่างกัน:
query One {
id
# First export "root", only then will be converted to "ROOT"
@export(as: "id")
@strUpperCase
again: id
# First convert to "ROOT" and then export this value
@strUpperCase
@export(as: "again")
}
query Two @depends(on: "One") {
mirrorID: _echo(value: $id)
mirrorAgain: _echo(value: $again)
}ผลลัพธ์:
{
"data": {
"id": "ROOT",
"again": "ROOT",
"mirrorID": "root",
"mirrorAgain": "ROOT"
}
}Multi-Field Directives
เมื่อฟีเจอร์ Multi-Field Directives เปิดใช้งานและเราส่งออกค่าของหลาย fields ไปยัง dictionary ให้ใช้ @deferredExport แทน @export เพื่อรับประกันว่า directives ทั้งหมดจาก fields ที่เกี่ยวข้องทุก field ถูกรันก่อนส่งออกค่าของ field
ตัวอย่างเช่น ใน query นี้ field แรกมี directive @strUpperCase ถูกใช้กับมัน และ field ที่สองมี @titleCase เมื่อรัน @deferredExport ค่าที่ส่งออกจะมี directives เหล่านี้ถูกใช้แล้ว:
query One {
id @strUpperCase # Will be exported as "ROOT"
again: id @titleCase # Will be exported as "Root"
@deferredExport(as: "props", affectAdditionalFieldsUnderPos: [1])
}
query Two @depends(on: "One") {
mirrorProps: _echo(value: $props)
}ผลลัพธ์:
{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}GraphQL spec
ฟังก์ชันนี้ยังไม่ได้เป็นส่วนหนึ่งของ GraphQL spec แต่มีการร้องขอแล้ว: