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

Interfaces และ Unions

จนถึงตอนนี้ทุก field มี object type เพียงตัวเดียวเป๊ะ ๆ แต่ domain จริง ๆ มี polymorphism: ผลการค้นหาอาจเป็น user หรือ post; การแจ้งเตือนอาจเป็นการกล่าวถึง การกดไลก์ หรือการติดตาม GraphQL มี abstract type สองแบบสำหรับสิ่งนี้ — interface และ union — และวิธีเลือก field ที่ถูกต้องต่อ type รูปธรรมแต่ละตัวโดยใช้ inline fragment บทเรียนนี้ครอบคลุมทั้งสามอย่าง และที่สำคัญคือ เมื่อใดควรใช้อันไหน

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

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
interface ใช้ field ร่วมกันในหมู่ type ที่เกี่ยวข้อง; union ลิสต์ทางเลือกที่ไม่เกี่ยวข้องกัน

เมื่อ 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 Media ของ Episode และ Movie คืนรายการที่ปนกัน และใช้ __typename บวกกับ inline fragment เพื่อดึง field ที่ถูกต้องจากแต่ละตัว กดปุ่ม Run

JavaScript

แต่ละรายการใน list คืนกลับมาพร้อมป้าย __typename ของตัวเอง และมีเฉพาะ inline fragment ที่ตรงกับ type นั้นเท่านั้นที่ใส่ field เพิ่มเข้ามา ตัว Episode พา seasonNumber มา ส่วน Movie พา rating มา หนึ่ง field สองรูปร่าง และปลอดภัยด้าน type เต็มที่

InterfaceUnion
ใช้เมื่อtype มี field ร่วมกันtype ไม่มี field ร่วมกัน
ตัวอย่างAnimalDog, CatSearchResultUser, Post, Tag
client queryfield จาก 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:

  • Node interface: ทุก object ที่มี id implement interface นี้
  • ทำให้ client ใช้ node(id: ID!) query เพื่อ fetch object ใด ๆ ด้วย global ID

Shopify:

  • SearchResult Union รวม Product, Collection, Page, Article
  • client ใช้ inline fragment เลือก field ตาม __typename
อะไรคือความแตกต่างที่นิยาม interface กับ union?
field __typename ในตัวคืนค่าอะไร?
คุณเลือก field ที่มีอยู่เฉพาะบน type ใด type หนึ่งของ union ได้อย่างไร?
สถานการณ์ใดเหมาะกับ union มากกว่า interface?