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

การออกแบบ Schema

ตอนนี้คุณอ่าน GraphQL schema เป็นแล้ว โมดูลนี้ว่าด้วยการ เขียน schema ให้ดี ตัว schema ไม่ใช่ผลพลอยได้จาก database แต่เป็น contract สาธารณะ ระหว่าง server ของคุณกับ client ทุกตัวที่เข้ามาเรียกใช้ ปล่อย field ออกไปแล้วย่อมมีคนพึ่งพา field นั้น การออกแบบ schema ที่ดีคือการเลือก type และ field ที่อธิบาย domain ได้ชัดเจนพอจน contract ยังซื่อสัตย์อยู่ได้แม้ product จะโตขึ้น

เมื่อคุณออกแบบ REST endpoint ตัว URL กับ JSON ที่คืนกลับมาผูกกันหลวม ๆ คุณแอบเพิ่ม field เข้าไปเงียบ ๆ ได้โดยไม่มีใครสังเกต ส่วน GraphQL schema ตรงกันข้าม — ชัดเจนและ strongly typed ทุก type ทุก field ทุก argument เขียนไว้ครบและระบบตรวจสอบให้ ฝั่ง client เขียน query โดยอิง contract นั้น เครื่องมือ generate code จาก contract และ server ของคุณก็สัญญาว่าจะรักษา contract ไว้

คำสัญญานี้คือหัวใจของทุกอย่าง ตัว schema ที่ออกแบบมาดีช่วยให้ทีม frontend ทำงานกับ type ที่มองเห็นได้จริง ช่วยให้เครื่องมือดักข้อผิดพลาดก่อน runtime และช่วยให้คุณประเมินผลกระทบของการเปลี่ยนแปลงได้ ต้นทุนคือวินัย — คุณต้องตัดสินใจตั้งแต่ต้นว่า domain นี้หน้าตาเป็นอย่างไร

ความผิดพลาดข้อใหญ่ที่สุดของคนที่เพิ่งเริ่มออกแบบ schema คือ map ตาม table ใน database แบบหนึ่งต่อหนึ่ง ตัว table เองออกแบบมาเพื่อการจัดเก็บ — foreign key, join table, column แบบ snake_case, flag ที่ denormalize ไว้ ส่วน schema ของคุณต้องออกแบบมาเพื่อ คำถามที่ client ถาม

ลองนึกถึงแอปห้องสมุดง่าย ๆ ฝั่ง database อาจเก็บ table books, table authors และ join table book_authors ส่วน client ไม่สนใจ join table แต่สนใจ Book ที่ มี authors และ Author ที่ มี books ตัว schema จึงแสดงความสัมพันธ์นั้นออกมาตรง ๆ:

type Book {
id: ID!
title: String!
authors: [Author!]!
}
type Author {
id: ID!
name: String!
books: [Book!]!
}

join table หายไปจาก contract ความสัมพันธ์ที่ client ไล่ไปตามจริง ๆ — จากหนังสือไปยังผู้แต่งแล้ววนกลับ — กลายเป็น field แบบ first-class ส่วนการเชื่อมไปยัง storage จริงเป็นหน้าที่ของ resolver ทีหลัง ตัว schema พูดด้วยภาษาของ domain

flowchart LR
  Query["Query (entry point)"]
  Book["type Book"]
  Author["type Author"]
  Scalars["Scalars: ID, String"]
  Query -->|"book(id)"| Book
  Book -->|"authors"| Author
  Author -->|"books"| Book
  Book --> Scalars
  Author --> Scalars
Schema คือกราฟของ type ใน domain และความสัมพันธ์ระหว่างกัน

โมดูลนี้ไล่ส่วนประกอบพื้นฐานทุกอย่างที่คุณต้องใช้ออกแบบ schema ให้สะอาด ทีละบทเรียน:

  1. การออกแบบ Schema (คุณอยู่ตรงนี้) — แนวคิดเรื่อง contract และการออกแบบรอบ domain
  2. Types และ Fields — object type, argument ของ field, description และวิธีที่ type เชื่อมโยงกัน
  3. Scalars และ Enums — scalar แบบ built-in และแบบ custom กับเหตุผลที่ควรใช้ enum แทน string อิสระ
  4. Interfaces และ Unions — จำลองรูปร่างที่ใช้ร่วมกันและทางเลือกแบบเลือกอย่างใดอย่างหนึ่ง แล้วแยกเคสด้วย fragment
  5. Inputs และ Nullability — input object type สำหรับ argument, เครื่องหมาย non-null !, list และค่า default

จบโมดูลนี้ คุณจะแปลงไอเดีย product ที่ยังคลุมเครือให้เป็น schema ที่แม่นยำ สื่อความหมายชัด และ query ได้ลื่นมือ

ตัวอย่างด้านล่างจำลองห้องสมุดข้างต้น ต่อ resolver แล้วรัน query ที่ข้ามจาก Book ไปยัง Author แล้ววนกลับ กดปุ่ม Run เพื่อรันบน GraphQL engine จริง

JavaScript

สังเกตว่า query อ่านได้เหมือนประโยคที่พูดถึง domain — หนังสือ ผู้แต่งของหนังสือ และหนังสือเล่มอื่นของผู้แต่งคนนั้น ไม่มี join table ไม่มี foreign key มีแต่ความสัมพันธ์ที่คนสนใจจริง ๆ ความอ่านง่ายแบบนี้คือสิ่งที่คุณได้จากการออกแบบ schema ที่ดี

ทำไมเราถึงเรียก GraphQL schema ว่าเป็น contract?
บทเรียนนี้เตือนถึงความผิดพลาดข้อไหนในการออกแบบ schema?
ในตัวอย่างห้องสมุด join table book_authors ไปอยู่ตรงไหนใน schema?