Interfaces และ Unions
จนถึงตอนนี้ทุก field มี object type เพียงตัวเดียวเป๊ะ ๆ แต่ domain จริง ๆ มี polymorphism: ผลการค้นหาอาจเป็น user หรือ post; การแจ้งเตือนอาจเป็นการกล่าวถึง การกดไลก์ หรือการติดตาม GraphQL มี abstract type สองแบบสำหรับสิ่งนี้ — interface และ union — และวิธีเลือก field ที่ถูกต้องต่อ type รูปธรรมแต่ละตัวโดยใช้ inline fragment บทเรียนนี้ครอบคลุมทั้งสามอย่าง และที่สำคัญคือ เมื่อใดควรใช้อันไหน
Interfaces — field ที่ใช้ร่วมกันข้าม type ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “Interfaces — field ที่ใช้ร่วมกันข้าม type ที่เกี่ยวข้อง”interface ประกาศชุดของ field ที่ object type หลายตัวล้วนสัญญาว่าจะมีให้ type ใด ๆ ที่ implements interface ต้องรวม field เหล่านั้นไว้ ใช้ interface เมื่อหลาย type เป็น รูปแบบที่แตกต่างของแนวคิดเดียวกัน อย่างแท้จริงและใช้ข้อมูลร่วมกัน:
interface Media { id: ID! title: String! durationSeconds: Int!}
type Episode implements Media { id: ID! title: String! durationSeconds: Int! seasonNumber: Int!}
type Movie implements Media { id: ID! title: String! durationSeconds: Int! rating: String}ทั้ง Episode และ Movie ต่างเป็น Media คือใช้ id, title และ durationSeconds ร่วมกัน แต่ต่างก็เพิ่ม field ของตัวเองเข้าไป ตอนนี้ field คืน interface ได้แล้ว — media: [Media!]! — และค่าจะเป็นหนึ่งใน type ที่ implement ไว้ ณ runtime
Unions — หนึ่งในหลาย type ที่ไม่เกี่ยวข้องกัน
หัวข้อที่มีชื่อว่า “Unions — หนึ่งในหลาย type ที่ไม่เกี่ยวข้องกัน”union บอกว่าค่าหนึ่งเป็น หนึ่งใน ชุด object type ที่ระบุไว้เป๊ะ ๆ โดย ไม่มี field ที่ต้องใช้ร่วมกัน ใช้ union เมื่อทางเลือกต่าง ๆ ไม่ได้ใช้ข้อมูลร่วมกันโดยธรรมชาติ แค่บังเอิญมาปรากฏในช่องเดียวกัน:
union SearchResult = Episode | Movie | Person
type Person { id: ID! name: String!}
type Query { search(term: String!): [SearchResult!]!}SearchResult หนึ่ง ๆ เป็น Episode, Movie หรือ Person ไม่มี field ใดที่ผลการค้นหาทุกตัวต้องมี — Person ไม่มีอะไรเหมือนกับ Movie เลยนอกจากโผล่มาในรายการเดียวกัน นั่นคือสัญญาณของ union มากกว่า interface
flowchart TD Interface["interface Media (shared: id, title, durationSeconds)"] Episode["type Episode"] Movie["type Movie"] Interface --> Episode Interface --> Movie Union["union SearchResult (no shared fields)"] Person["type Person"] Union --> Episode Union --> Movie Union --> Person
__typename และ inline fragment
หัวข้อที่มีชื่อว่า “__typename และ inline fragment”เมื่อ field คืน type รูปธรรมได้มากกว่าหนึ่งตัว client ต้องมีวิธีถามว่า “นี่คือ type ไหน และขอ field เฉพาะของ type นั้นด้วย” GraphQL มี field __typename มาให้ในตัว ซึ่งคืนชื่อ type รูปธรรมเป็น string และมี inline fragment ไว้เลือก field แบบมีเงื่อนไขราย type:
{ search(term: "graph") { __typename ... on Episode { title seasonNumber } ... on Movie { title rating } ... on Person { name } }}อ่าน ... on Movie ว่า “ถ้าผลลัพธ์นี้เป็น Movie ให้เลือก field เหล่านี้ด้วย” สำหรับ interface คุณเลือก field ที่ใช้ร่วมกันได้ตรง ๆ (ไม่ต้องใช้ fragment) แล้วใช้ inline fragment เฉพาะกับส่วนเสริมของแต่ละ type เท่านั้น __typename แทบจะคุ้มเสมอที่จะขอมาด้วยบน field ที่เป็น polymorphic เพราะ client ใช้ตัดสินว่าจะรัน code สาขาไหนของตัวเอง
union พร้อม fragment รันได้จริง
หัวข้อที่มีชื่อว่า “union พร้อม fragment รันได้จริง”ตัวอย่างด้านล่างนิยาม union Media ของ Episode และ Movie คืนรายการที่ปนกัน และใช้ __typename บวกกับ inline fragment เพื่อดึง field ที่ถูกต้องจากแต่ละตัว กดปุ่ม Run
แต่ละรายการใน list คืนกลับมาพร้อมป้าย __typename ของตัวเอง และมีเฉพาะ inline fragment ที่ตรงกับ type นั้นเท่านั้นที่ใส่ field เพิ่มเข้ามา ตัว Episode พา seasonNumber มา ส่วน Movie พา rating มา หนึ่ง field สองรูปร่าง และปลอดภัยด้าน type เต็มที่
| Interface | Union | |
|---|---|---|
| ใช้เมื่อ | type มี field ร่วมกัน | type ไม่มี field ร่วมกัน |
| ตัวอย่าง | Animal → Dog, Cat | SearchResult → User, Post, Tag |
| client query | field จาก interface ได้ตรง | ต้องใช้ inline fragment ทุกครั้ง |
| เพิ่ม type ใหม่ | type ใหม่ต้อง implement interface | เพิ่ม member ใน union ได้โดยตรง |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”Interface เมื่อไม่มี Shared Field จริง อาการ:
- สร้าง interface
Nodeที่มีแค่id: ID!โดยไม่มี shared behavior - ทุก type implement แต่ไม่ได้ใช้ polymorphism จริง
- Interface มีค่าเมื่อมี field หรือ behavior ที่ใช้ร่วมกันจริง ๆ
Union ที่มีแค่ 1 Member อาการ:
union UserResult = User— Union ที่มีแค่ type เดียว- เพิ่ม indirection โดยไม่มีประโยชน์
- ใช้ type ตรง ๆ หรือรอจนมี multiple member จริงก่อน
💡 ตัวอย่างจากของจริง
GitHub:
Nodeinterface: ทุก object ที่มีidimplement interface นี้- ทำให้ client ใช้
node(id: ID!)query เพื่อ fetch object ใด ๆ ด้วย global IDShopify:
SearchResultUnion รวมProduct,Collection,Page,Article- client ใช้ inline fragment เลือก field ตาม
__typename