Federation
GraphQL service เดียวเป็นคำตอบที่ใช้ได้ไปอีกนาน แต่พอบริษัทโตขึ้น schema เดียวที่ทีมเดียวดูแลจะกลายเป็นคอขวด เพราะทุกการเปลี่ยนแปลงต้องลอดผ่าน codebase เดียว deploy เดียว และ reviewer ชุดเดียว Federation คือ pattern ที่ให้หลายทีมเป็นเจ้าของกราฟคนละชิ้น โดยที่ client ยังเห็น API เดียวที่รวมเป็นหนึ่ง
หนึ่งกราฟ หลายเจ้าของ
หัวข้อที่มีชื่อว่า “หนึ่งกราฟ หลายเจ้าของ”แนวคิดคือการแยกกราฟออกเป็น subgraph — GraphQL service อิสระ แต่ละตัวเป็นเจ้าของ type และ field ของ domain เดียว ทีม accounts เป็นเจ้าของ User ทีม catalog เป็นเจ้าของ Product ทีม reviews เป็นเจ้าของ Review แต่ละ subgraph ก็คือ GraphQL server ปกติที่รันแยกเดี่ยว ๆ ได้
ด้านหน้าของ subgraph ทั้งหมดจะมี gateway (หรือ router) คอยรับงาน gateway อ่าน schema ของทุก subgraph แล้ว compose เข้าเป็น schema รวมหนึ่งเดียวที่เรียกว่า supergraph จากนั้นเปิด schema เดียวนั้นให้ client ใช้ ฝั่ง client ส่ง query แค่ครั้งเดียว gateway จะคิดเองว่าต้องเรียก subgraph ไหนบ้าง ตามลำดับใด แล้วเย็บชิ้นส่วนกลับมาเป็น response เดียว
flowchart TD Client["Client (one query)"] Gateway["Gateway / Router"] Super["Supergraph (composed schema)"] Accounts["accounts subgraph (User)"] Catalog["catalog subgraph (Product)"] Reviews["reviews subgraph (Review)"] Client --> Gateway Gateway --> Super Gateway --> Accounts Gateway --> Catalog Gateway --> Reviews Reviews -->|"author: User via @key id"| Accounts
ข้อได้เปรียบจริง ๆ เป็นเรื่อง องค์กร ทีม reviews ปล่อยการเปลี่ยนแปลงของ Review ได้เลย โดยไม่ต้องแตะหรือรอประสานงานกับทีม accounts กราฟจึงขยายไปตามผังองค์กร แทนที่จะขัดกับผังองค์กร
Entities และ references — subgraphs เชื่อมกันอย่างไร
หัวข้อที่มีชื่อว่า “Entities และ references — subgraphs เชื่อมกันอย่างไร”สิ่งที่ทำให้ Review เชื่อมไปหา User ได้ทั้งที่อยู่คนละ service คือ entity ซึ่งก็คือ type ที่ subgraph หลายตัวมาช่วยกันเติมได้ โดยระบุตัวตนด้วย key คุณ mark ด้วย @key พร้อมบอก field ที่ใช้ระบุตัวตนแบบไม่ซ้ำ
subgraph accounts นิยาม entity User:
# accounts subgraphtype User @key(fields: "id") { id: ID! name: String!}
type Query { me: User}subgraph reviews อ้างอิง User โดยไม่ได้เป็นเจ้าของข้อมูล จึงประกาศ entity ด้วย extend โดยใช้ key เดียวกัน พร้อม placeholder ของ field ที่ต้องใช้ แล้วเพิ่ม link author ของตัวเองเข้าไป:
# reviews subgraphtype Review { id: ID! body: String! author: User!}
extend type User @key(fields: "id") { id: ID! @external}เมื่อ client ขอ review { author { name } } gateway จะเรียก subgraph reviews เพื่อเอา review กับ author.id มาก่อน จากนั้นขอให้ subgraph accounts resolve the reference — “เอา User ที่มี id นี้มาให้หน่อย” แต่ละ subgraph จะ implement reference resolver ที่แปลง key ให้กลายเป็น entity เต็ม:
// accounts subgraph resolversconst resolvers = { User: { // The gateway hands over the key; the owning subgraph returns the entity. __resolveReference(ref: { id: string }) { return findUserById(ref.id); }, },};__resolveReference คือรอยต่อ (seam) ของ federation ฝั่ง subgraph reviews ไม่เคย query database ของ accounts เลย แค่ส่ง reference ของ User ออกมาเป็น id แล้ว subgraph เจ้าของข้อมูลก็เติมส่วนที่เหลือให้ ความเป็นเจ้าของจึงยังชัดเจน แม้ type เดียวจะมีหลาย service มาช่วยเสริม
กฎการประกอบทำให้กราฟซื่อสัตย์
หัวข้อที่มีชื่อว่า “กฎการประกอบทำให้กราฟซื่อสัตย์”gateway ไม่ได้รวม schema แบบมั่ว ๆ แต่จะ validate composition ก่อน subgraph สองตัวจะนิยาม field เดียวกันบน type เดียวกันแบบขัดแย้งกันไม่ได้ ทุก entity ที่ถูกอ้างถึงต้องมีนิยามอยู่ที่ใดที่หนึ่งพร้อม @key ที่ตรงกัน และ field @external ต้องมีอยู่จริงบน subgraph เจ้าของ ถ้า compose ไม่ผ่าน supergraph จะไม่ถูก publish แปลว่าการเปลี่ยนแปลงที่พังถูกจับได้ก่อน client คนไหนจะเห็น composition มีบทบาทกับ federation เหมือนที่ code generation มีกับบทที่แล้ว คือให้เครื่องตรวจว่า contract ที่รวมกันแล้วยังสอดคล้องกันอยู่
federate ตาม domain ไม่ใช่ตาม layer
หัวข้อที่มีชื่อว่า “federate ตาม domain ไม่ใช่ตาม layer”ความผิดพลาดที่พบบ่อยที่สุดของ federation คือการแยกตาม layer ทางเทคนิค เช่น “database subgraph” หรือ “validation subgraph” วิธีนี้สร้างคอขวดขึ้นมาใหม่ เพราะทุกฟีเจอร์ยังต้องแตะทุก layer อยู่ดี ให้แยกตาม domain แทน — accounts, catalog, reviews, billing แต่ละ subgraph จะได้ตรงกับทีมที่เป็นเจ้าของงานแนวตั้งทั้งชิ้นแบบ end to end และรอยต่อของ @key/reference ก็จะตกลงบนขอบเขต domain ตามธรรมชาติ (review มี author หนึ่งคน, product มี reviews)
| ข้อดี | ข้อแลกเปลี่ยน |
|---|---|
| แต่ละ team ดูแล subgraph ของตัวเอง — schema ownership ชัดเจน | infrastructure ซับซ้อนกว่า monolithic schema |
extend type ข้าม subgraph ได้ — User ใน product service มี field จาก review service | learning curve สูง — ต้องเข้าใจ entity, @key, @external |
| deploy subgraph อิสระโดยไม่กระทบ subgraph อื่น | gateway เป็น single point of failure ถ้าไม่ design ดี |
| scale แต่ละ subgraph ตาม load ของตัวเอง | subgraph ที่ schema ไม่ compatible ทำให้ federation fail |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”Federation ตั้งแต่ต้น โดยไม่มีหลาย Team อาการ:
- 1 team ดูแล subgraph ทั้งหมด
- ซับซ้อนกว่า monolithic schema โดยไม่ได้ประโยชน์
- เริ่มด้วย monolithic schema แล้ว federate เมื่อ team แยกกันจริง
Circular Entity Reference อาการ:
Usersubgraph referenceOrderและOrdersubgraph referenceUserแบบ circular- gateway ต้องทำ round trip ซ้ำซ้อนเพื่อ resolve
- ออกแบบ entity ownership ให้ชัดเจน — ใคร owns ใคร
💡 ตัวอย่างจากของจริง
Airbnb:
- ใช้ Apollo Federation สำหรับ internal GraphQL platform
- แต่ละ team ดูแล subgraph: listing, booking, user, review
- schema gateway รวม subgraph สำหรับ mobile และ web client
Netflix:
- Federated GraphQL ช่วยให้ 100+ team ทำงานอิสระ
- schema registry ติดตาม breaking change ก่อน deploy