การเขียนข้อมูลและการทำงานแบบเรียลไทม์
จนถึงตอนนี้ graph ของเรายังอ่านได้อย่างเดียว เราเลือก field เดินตามลิงก์ และ resolve ค่าออกมา แต่ไม่เคยเปลี่ยนอะไรเลย ส่วน API จริง ๆ ต้องเปิดให้ client ลงมือทำ ได้ เช่น โพสต์รีวิว เก็บเพลงเข้าคลัง หรือกดติดตามศิลปิน และเมื่อข้อมูลเปลี่ยนได้ client ก็มักอยากรู้ทันทีที่ข้อมูลเปลี่ยน ความต้องการสองอย่างนี้แมปเข้ากับ root type อีกสองตัวที่เหลือพอดี คือ Mutation สำหรับการเขียน และ Subscription สำหรับสตรีมของการเปลี่ยนแปลงที่ตามมา
ประตูบานใหม่สองบาน
หัวข้อที่มีชื่อว่า “ประตูบานใหม่สองบาน”คุณได้รู้จัก root type ทั้งสามตัวมาแล้วในโมดูล foundations ในบทนี้เราจะนำตัวที่สองและสามมาใช้งานจริง
Mutation— จุดเข้าสำหรับการเขียน แต่ละ field ในนี้แทนการเปลี่ยนแปลงที่ตั้งใจหนึ่งอย่าง เช่น สร้าง อัปเดต หรือลบ โดยรันการเปลี่ยนแปลงนั้นแล้วคืนข้อมูลที่อธิบายผลลัพธ์กลับมาSubscription— จุดเข้าสำหรับเรียลไทม์ แต่ละ field ในนี้เปิดสตรีมที่อยู่ยาวนาน client subscribe เพียงครั้งเดียว จากนั้นเซิร์ฟเวอร์จะ push payload ใหม่ทุกครั้งที่ event ที่เกี่ยวข้องเกิดขึ้น
แบบจำลองทางความคิดคือ request ที่ ลงมือทำ (mutation) เทียบกับ connection ที่ เฝ้าฟัง (subscription) ตัว mutation คือ round trip เดียว ส่งการเปลี่ยนแปลงไป แล้วรับผลลัพธ์กลับมา ส่วน subscription คือช่องทางที่เปิดค้างไว้ ขอครั้งเดียวแต่ทยอยได้ผลลัพธ์หลายครั้งตามเวลา
flowchart LR
subgraph Read
Q["Query"] --> R1["one result"]
end
subgraph Write
M["Mutation"] --> S["mutable store"]
S --> R2["result of the change"]
end
subgraph RealTime
Sub["Subscription"] --> Ev["event stream"]
Ev --> P1["payload"]
Ev --> P2["payload"]
Ev --> P3["payload …"]
end หน้าตาของ mutation เป็นอย่างไร
หัวข้อที่มีชื่อว่า “หน้าตาของ mutation เป็นอย่างไร”field ของ mutation ประกาศไว้บน type Mutation โดยปกติจะรับ argument ที่อธิบายการเปลี่ยนแปลง แล้วคืน object ที่ได้รับผลกระทบกลับมา เพื่อให้ client อัปเดตมุมมองของตัวเองได้โดยไม่ต้องอ่านซ้ำอีกรอบ
type Mutation { addReview(trackId: ID!, rating: Int!, body: String): Review!}การเรียกใช้หน้าตาคล้าย query เกือบทั้งหมด ต่างกันตรงที่เริ่มด้วย mutation แทน { (ซึ่งย่อมาจาก query) และเซิร์ฟเวอร์ได้รับอนุญาตให้เปลี่ยน state
mutation { addReview(trackId: "t1", rating: 5, body: "On repeat all week.") { id rating body }}โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”ตลอดสี่บทเรียนถัดไป เราจะไต่ระดับจาก “ใช้งานได้” ไปสู่ “สเกลได้”
- การออกแบบ mutation — การตั้งชื่อการเขียนตาม intent การจำกัดให้หนึ่ง mutation ต่อหนึ่งการเปลี่ยนแปลงเชิงตรรกะ และการคืนข้อมูลที่ได้รับผลกระทบ
- รูปแบบ input / payload — การห่อ argument ไว้ใน object
inputเดียว และคืนpayloadที่มีโครงสร้าง เพื่อให้ mutation วิวัฒน์ได้และพก error ราย field ติดไปด้วย - Subscription — การจำลองเรียลไทม์ให้เป็นสตรีมของ event ด้วย
subscribeและresolveรวมถึง transport ที่ใช้ส่ง และจังหวะที่ ไม่ควร หยิบ subscription มาใช้ - แนวปฏิบัติที่ดีของ mutation — idempotency การ validate, user error เทียบกับ protocol error, ความ chatty และ optimistic UI
mutation ที่รันได้จริง
หัวข้อที่มีชื่อว่า “mutation ที่รันได้จริง”ตัวอย่างด้านล่างสร้าง schema ขนาดเล็กที่มี type Mutation มี store รีวิวในหน่วยความจำที่เปลี่ยนค่าได้ และมี resolver ที่ append ลงไปในนั้น กด Run เพื่อเพิ่มรีวิวและดู store เติบโตขึ้น query ตัวที่สองจะอ่านสิ่งที่ mutation เขียนไว้กลับมาตรงเป๊ะ
สังเกตรูปร่างของผลลัพธ์ให้ดี ตัว mutation คืน Review ตัวเดียวที่เพิ่งสร้าง พร้อม id ที่เซิร์ฟเวอร์กำหนดมาให้ครบถ้วน ดังนั้น client ไม่ต้องเดาเอง ส่วนการอ่านที่ตามมาเป็นตัวพิสูจน์ว่าการเขียนลงจริง จาก store ที่เคยว่างเปล่า ตอนนี้มีรีวิวอยู่หนึ่งรายการพอดี จังหวะ ลงมือทำ แล้วรับผลลัพธ์ ใน round trip เดียวแบบนี้ คือจังหวะของทุก mutation ในโมดูลนี้