ข้ามไปยังเนื้อหา

การเขียนข้อมูลและการทำงานแบบเรียลไทม์

จนถึงตอนนี้ 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
Read คืนค่าครั้งเดียว; mutation เขียนแล้วคืนค่า; subscription push หลาย payload จากสตรีมของ event

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

ตัวอย่างด้านล่างสร้าง schema ขนาดเล็กที่มี type Mutation มี store รีวิวในหน่วยความจำที่เปลี่ยนค่าได้ และมี resolver ที่ append ลงไปในนั้น กด Run เพื่อเพิ่มรีวิวและดู store เติบโตขึ้น query ตัวที่สองจะอ่านสิ่งที่ mutation เขียนไว้กลับมาตรงเป๊ะ

JavaScript

สังเกตรูปร่างของผลลัพธ์ให้ดี ตัว mutation คืน Review ตัวเดียวที่เพิ่งสร้าง พร้อม id ที่เซิร์ฟเวอร์กำหนดมาให้ครบถ้วน ดังนั้น client ไม่ต้องเดาเอง ส่วนการอ่านที่ตามมาเป็นตัวพิสูจน์ว่าการเขียนลงจริง จาก store ที่เคยว่างเปล่า ตอนนี้มีรีวิวอยู่หนึ่งรายการพอดี จังหวะ ลงมือทำ แล้วรับผลลัพธ์ ใน round trip เดียวแบบนี้ คือจังหวะของทุก mutation ในโมดูลนี้

root type ตัวใดเป็นจุดเข้าสำหรับการเขียนใน GraphQL schema?
subscription ต่างจาก query อย่างไรในแง่ของผลลัพธ์?
หลังจาก mutation สร้างเรกคอร์ดแล้ว ทำไมโดยทั่วไปจึงคืน object ที่ได้รับผลกระทบกลับมา?