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

Types และ Fields

object type คือหัวเรี่ยวหัวแรงของ GraphQL schema เกือบทุกอย่างที่ client สัมผัสได้คือ object type ที่มี field และวิธีที่คุณเลือก field เหล่านั้น — ทั้งชื่อ, type และ argument ที่รับ — คือเนื้อแท้ส่วนใหญ่ของการออกแบบ schema บทเรียนนี้ลงลึกเรื่อง object type และสี่สิ่งที่คุณทำกับ field ได้

object type คือกลุ่มของ field ที่มีชื่อกำกับ แต่ละ field มีทั้งชื่อและ type ตัวอย่างด้านล่างคือ type ที่จำลองสูตรอาหารในแอปทำอาหาร:

type Recipe {
id: ID!
title: String!
servings: Int!
prepMinutes: Int
}

Recipe มีสี่ field โดยสามตัว resolve เป็น scalar — ID หนึ่งตัว String หนึ่งตัว และ Int สองตัว ส่วน type ของแต่ละ field คือคำสัญญาว่า client จะได้อะไรกลับไป ชื่อ field ควรอ่านแล้วเหมือนคุณสมบัติของสิ่งนั้น เช่น title, servings, prepMinutes เลือกคำนามใน domain ที่ชัดเจนแทนตัวย่อ เพราะคนอ่าน schema บ่อยกว่าเขียนมาก

field รับ argument ได้เหมือน parameter ของฟังก์ชัน ตัว argument ช่วยให้ field เดียวรองรับคำขอได้หลายรูปแบบ การใช้งานคลาสสิกคือ field สำหรับค้นหาบน root type Query:

type Query {
recipe(id: ID!): Recipe
recipes(first: Int, search: String): [Recipe!]!
}

recipe รับ id แบบบังคับ แล้วคืนสูตรอาหารหนึ่งรายการ (หรือ null ถ้าไม่มีรายการไหนตรง) ส่วน recipes รับ argument first กับ search แบบ optional ไว้แบ่งหน้าและกรองรายการ ทั้งนี้ argument ไม่ได้จำกัดอยู่แค่ field ระดับ root — field ไหนก็รับได้ รูปแบบที่พบบ่อยคือให้ field ปรับรูปร่างผลลัพธ์ของตัวเอง:

type Recipe {
id: ID!
title: String!
description(maxLength: Int): String
}

ตรงนี้ client ขอ description แบบตัดสั้นได้ด้วยการส่ง maxLength มา แล้ว resolver ก็ตัดให้ สังเกตว่า argument วางอยู่บน field ที่ใช้งานจริง

GraphQL มีระบบเอกสารมาให้ในตัว ทุก type ทุก field ทุก argument ใส่ description ได้ด้วยการเขียน string literal ไว้เหนือบรรทัดนั้น ตัว description จะโผล่ในเครื่องมือต่าง ๆ ทั้งใน GraphiQL และในเอกสารที่ generate ออกมา:

"""
A single dish a user can cook, including timing and serving information.
"""
type Recipe {
"Stable unique identifier for the recipe."
id: ID!
"Human-readable name shown in listings and headers."
title: String!
"How many portions the recipe yields."
servings: Int!
}

บล็อกที่ใช้เครื่องหมายคำพูดสามตัวรองรับข้อความหลายบรรทัด ส่วน string เครื่องหมายคำพูดเดี่ยวก็พอสำหรับบรรทัดเดียว description ไม่ใช่เครื่องประดับ แต่เป็นส่วนหนึ่งของ contract ฝั่งที่มนุษย์อ่าน เขียนเหมือนกำลังอธิบาย field ให้ developer ที่ไม่เคยเห็น domain ของคุณมาก่อนฟัง

ความสัมพันธ์ — วิธีที่ type เชื่อมโยงกันเป็นกราฟ

หัวข้อที่มีชื่อว่า “ความสัมพันธ์ — วิธีที่ type เชื่อมโยงกันเป็นกราฟ”

ในวินาทีที่ type ของ field เป็น object type อีกตัวหนึ่ง schema ของคุณก็เลิกเป็นรายการของระเบียนแบน ๆ และกลายเป็น กราฟ สูตรอาหารมีผู้แต่ง ผู้แต่งมีสูตรอาหาร:

type Recipe {
id: ID!
title: String!
author: Author!
}
type Author {
id: ID!
name: String!
recipes: [Recipe!]!
}

field author บน Recipe ชี้ไปที่ type Author และ field recipes บน Author ก็ชี้กลับมา client จึงเริ่มจากสูตรอาหาร ก้าวเข้าไปหาผู้แต่ง แล้วเดินต่อไปยังสูตรอื่น ๆ ของผู้แต่งคนนั้นได้ในการ query ครั้งเดียว field ที่เชื่อมโยงแบบนี้คือ edge ของกราฟ และการเลือก edge ให้ดีคือสิ่งที่ทำให้ API รู้สึกเป็นเนื้อเดียวกัน

flowchart LR
  Query["Query"]
  Recipe["type Recipe"]
  Author["type Author"]
  Args["Field arg: recipe(id: ID!)"]
  Query --> Args
  Args --> Recipe
  Recipe -->|"author"| Author
  Author -->|"recipes"| Recipe
Object type ที่เชื่อมโยงกันด้วย field ก่อเกิดเป็นกราฟที่ client เดินผ่าน

ตัวอย่างด้านล่างนิยาม Recipe และ Author ให้ field recipe มี argument และรัน query ที่เดินผ่านความสัมพันธ์ระหว่างสอง type นี้ กดปุ่ม Run

JavaScript

query ส่ง id เป็น argument ได้รับ Recipe หนึ่งรายการ จากนั้นก้าวเข้าไปใน author และออกมายังรายการสูตรอาหารของผู้แต่งคนนั้น หนึ่งรอบการสื่อสาร หลาย type — นั่นคือกราฟที่กำลังทำงาน

ข้อดีข้อแลกเปลี่ยน
type system เป็น living documentation ที่ accurate เสมอschema ใหญ่ที่ไม่ organize ดีอ่านยาก
rename field โดยใช้ @deprecated แทนการลบ — backward compattype ที่ leak database detail ทำให้ refactor ยาก
field-level description ทำให้ introspection เป็น API doctype ที่ generic เกิน เช่น JSON ลด type safety
non-null ! บังคับ client จัดการ null อย่างถูกต้องnull vs non-null design ผิด ทำให้ client ต้อง null check ที่ไม่จำเป็น

Type ที่สะท้อน Database Schema ตรง ๆ อาการ:

  • GraphQL type ชื่อ users_table หรือ field ชื่อ user_id แทน userId
  • expose column name จาก database ให้ client โดยตรง
  • schema ควรสะท้อน business domain ไม่ใช่ database implementation

JSON Scalar ทุกที่ที่ไม่รู้จะทำยังไง อาการ:

  • metadata: JSON แทนที่จะออกแบบ type ให้ชัดเจน
  • client ไม่รู้ว่า metadata มี field อะไร — ไม่มี type checking
  • หากยังไม่รู้ shape ใช้ JSON ชั่วคราวได้ แต่วางแผน migrate ไป typed field

💡 ตัวอย่างจากของจริง

GitHub:

  • ใช้ @deprecated(reason: "...") แทนการลบ field
  • migration period — field ยังคง serve ต่อไป แต่ client รู้ว่าต้องอัปเดต

Shopify:

  • schema design ยึด “client first” — field ทุกตัวออกแบบจาก client use case ไม่ใช่ database
อะไรที่เปลี่ยนกลุ่มของ object type แบน ๆ ให้กลายเป็นกราฟที่เดินผ่านได้?
argument ของ field สามารถประกาศไว้ที่ไหนได้บ้าง?
ใน SDL คุณแนบเอกสารให้กับ type หรือ field ได้อย่างไร?