Type System
เราเรียก schema ว่า “contract” มาหลายครั้งแล้ว ตอนนี้มาเปิดดูข้างในกัน schema สร้างจากชุด type kind เล็ก ๆ ที่เขียนด้วย Schema Definition Language (SDL) เรียนรู้ building block เหล่านี้แล้วคุณจะอ่าน — และออกแบบ — GraphQL API ตัวไหนก็ได้
object type และ field
หัวข้อที่มีชื่อว่า “object type และ field”type kind ที่พบบ่อยที่สุดคือ object type: ชุดของ field ที่มีชื่อ แต่ละ field มีชื่อและ type และอาจรับ argument ได้ ด้านล่างคือ object type ที่อธิบาย track ในไลบรารีเพลง:
type Track { id: ID! title: String! durationSeconds: Int! explicit: Boolean artist: Artist!}Track มีห้า field type ของแต่ละ field บอก client ว่าจะได้อะไรกลับมา ทั้ง ID หนึ่งตัว ค่า String และ Int บางตัว Boolean หนึ่งตัว และลิงก์ไปยัง object type อีกตัวคือ Artist field สุดท้ายนี่แหละที่เปลี่ยน record แบน ๆ ให้กลายเป็น graph เพราะ field ชี้ไปหา type อื่นได้ และ client ก็เดินผ่านไปต่อได้
scalar — ค่าที่เป็นใบไม้
หัวข้อที่มีชื่อว่า “scalar — ค่าที่เป็นใบไม้”การเลือกทุกครั้งสุดท้ายจะลงเอยที่ scalar: ค่าเดี่ยวที่แบ่งย่อยไม่ได้ GraphQL มาพร้อม built-in scalar ห้าตัว:
Int— จำนวนเต็มแบบมีเครื่องหมายขนาด 32 บิตFloat— ค่าทศนิยมแบบ double-precision ที่มีเครื่องหมายString— ข้อความ UTF-8Boolean—trueหรือfalseID— ตัวระบุที่ไม่ซ้ำ serialize เป็น string แต่เชิงความหมายเป็นแบบทึบ (ไม่ควรเอาไปคำนวณ)
คุณยังสามารถนิยาม custom scalar ได้ (เช่น DateTime หรือ EmailAddress) เพื่อแนบกฎการ validate และ serialize แต่ทั้งห้าตัวข้างต้นคือรากฐาน
root type
หัวข้อที่มีชื่อว่า “root type”object type สามตัวพิเศษกว่าตัวอื่น เพราะเป็น จุดเข้า สู่ graph query ต้องเริ่มจากที่ไหนสักแห่ง และสามชื่อนี้คือจุดเริ่ม:
Query— จุดเข้าสำหรับการอ่าน การอ่านทุกครั้งเริ่มด้วยการเลือก field ที่นี่Mutation— จุดเข้าสำหรับการเขียน field ที่นี่สร้าง อัปเดต หรือลบข้อมูลSubscription— จุดเข้าสำหรับ real-time field ที่นี่ stream การอัปเดตตามเวลา
มีเพียง Query เท่านั้นที่จำเป็น schema ที่มีการอ่านแต่ไม่มีการเขียนก็แค่ละ Mutation และ Subscription ออกไป
flowchart TD Query["Query (read entry)"] Mutation["Mutation (write entry)"] Subscription["Subscription (stream entry)"] Track["type Track"] Artist["type Artist"] Scalars["Scalars: ID, String, Int, Boolean, Float"] Query --> Track Mutation --> Track Track -->|"artist field"| Artist Track --> Scalars Artist --> Scalars
nullability — เครื่องหมายอัศเจรีย์
หัวข้อที่มีชื่อว่า “nullability — เครื่องหมายอัศเจรีย์”โดยค่าเริ่มต้น ทุก field ใน GraphQL เป็น nullable คือจะคืนค่าหรือคืน null ก็ได้ เติม ! ต่อท้ายเมื่อไหร่ field ก็กลายเป็น non-null คือ server รับประกันว่ามีค่าจริง ไม่มีวันเป็น null ให้อ่าน ! ว่า “รับประกัน”:
type Artist { id: ID! # always present name: String! # always present bio: String # may be null — biography is optional tracks: [Track!]! # a non-null list of non-null Tracks}บรรทัดสุดท้ายนั้นคุ้มค่าที่จะถอดความ [Track!]! คือ list type ที่มีเครื่องหมายอัศเจรีย์สองตัวทำงานสองอย่างต่างกัน:
Track!ตัวในหมายความว่าทุก element ในลิสต์เป็นTrackแบบ non-null!ตัวนอกแปลว่าตัว list เองไม่มีวันเป็นnull(แต่ยังว่าง[]ได้)
ดังนั้น tracks คืน list เสมอ และใน list นั้นไม่มีวันมีรู null nullability เป็นส่วนหนึ่งของ contract เพราะบอก client ชัด ๆ ว่า field ไหนต้องระวัง และ field ไหนไว้ใจได้
schema เล็ก ๆ ที่รันจริง
หัวข้อที่มีชื่อว่า “schema เล็ก ๆ ที่รันจริง”ตัวอย่างด้านล่างนิยาม schema สอง type — Artist กับ Track — ต่อ resolver แล้วรัน query ที่ข้ามจาก type หนึ่งไปอีก type หนึ่ง กด Run เพื่อรันด้วย GraphQL engine จริง
ดูผลลัพธ์ใกล้ ๆ title กับ durationSeconds กลับมาเป็นค่าที่รับประกันได้ เพราะประกาศเป็น non-null ใน schema ส่วน artist เป็น object ที่ซ้อนอยู่ — query ก้าวจาก Track เข้าไปใน Artist — และข้างในนั้น bio กลับมาเป็น null ซึ่งทำได้ เพราะ bio ประกาศไว้โดยไม่มี ! type system ทำนายรูปร่างทั้งหมดนี้ได้ตั้งแต่ก่อน resolver จะรันด้วยซ้ำ
| ข้อดี | ข้อแลกเปลี่ยน |
|---|---|
| validate query ก่อน runtime — error เจอตั้งแต่ development | type system เพิ่ม verbosity ใน schema definition |
| auto-complete และ type checking ใน IDE | schema migration ต้องระวัง backward compatibility |
| code generation สำหรับ TypeScript type จาก schema | nullable by default ทำให้ต้อง null check ที่ client |
| introspection ทำให้ tooling และ playground ทำงานได้ | type ที่ซับซ้อนทำให้ schema อ่านยากขึ้น |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”Nullable ทุก field โดยไม่คิด อาการ:
- schema มี
Stringแทนที่จะเป็นString!ทุกที่ - client ต้อง null check ทุก field แม้ field นั้นจะไม่มีทางเป็น null จริง ๆ
- ออกแบบ nullability อย่างตั้งใจ —
!เมื่อมั่นใจ, nullable เมื่อ field อาจไม่มีค่า
Schema สะท้อน Database Table ตรง ๆ อาการ:
- type ใน schema ชื่อเดียวกับ table ใน database
- field ใน schema = column ใน database ทุกตัว
- expose implementation detail ให้ client โดยไม่จำเป็น
💡 ตัวอย่างจากของจริง
GitHub GraphQL API:
- ใช้ custom scalar เช่น
URI,DateTime,HTMLนอกเหนือจาก built-in- type system ทำให้ client รู้ว่า field ไหน return URL, รูปแบบวันที่, หรือ HTML string
Shopify:
- ใช้ Interface สำหรับ Node (ทุก object ที่มี
id) เพื่อให้ cache ง่าย- ใช้ Union สำหรับ search result ที่อาจเป็น Product, Collection, หรือ Page