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

API Gateway

คุณมี service อยู่นับสิบตัว แต่ละตัวมี API และที่อยู่บนเครือข่ายของตัวเอง ส่วนแอปมือถือหรือ single-page web client ต้องดึงข้อมูลจากหลาย service เพื่อ render หน้าจอเดียว แค่หน้าสรุป order หน้าเดียวก็ต้องแตะทั้ง Orders, Inventory และ Customer ถ้าปล่อยให้ client คุยกับแต่ละ service ตรง ๆ client ก็ต้องรู้ที่อยู่ของทุกตัว ต้องวิ่ง round trip หลายรอบบนเครือข่ายมือถือที่ช้า และต้องยืนยันตัวตนกับทุก service

การเปิดเผยบริการภายในต่อ client ภายนอกโดยตรงทำให้ client เหล่านั้นผูกมัดกับโครงสร้างภายในของคุณ client ต้องค้นหาและเรียก endpoint มากมาย และทุกครั้งที่คุณแยกหรือรวมบริการ client ทุกตัวก็จะพัง ที่แย่กว่านั้นคือ cross-cutting concerns — การยืนยันตัวตน การ terminate TLS การจำกัดอัตรา การ logging คำขอ — จะต้องถูก implement ในทุกบริการหรือในทุก client และ client ที่พูดมากบนการเชื่อมต่อที่มี latency สูงต้องจ่ายค่าวิ่งไปกลับหนึ่งรอบต่อหนึ่งบริการเพียงเพื่อเติมข้อมูลหน้าจอเดียว คุณจะให้ client ภายนอกมีที่เรียกที่เดียวที่เสถียร มีประสิทธิภาพ และปลอดภัยได้อย่างไร โดยไม่รั่วไหลโครงสร้างภายในของคุณ?

API Gateway คือ server ตัวเดียวที่คั่นอยู่ระหว่าง client ภายนอกกับ service ภายใน ทำหน้าที่เป็นจุดเข้าจุดเดียวของทั้งระบบ และรับผิดชอบสามอย่าง:

  • Routing (การจัดเส้นทาง) — รับทุก request จากภายนอกแล้วส่งต่อไปยัง service ที่ถูกต้อง โดยจับคู่ path สาธารณะเข้ากับที่อยู่ภายใน ฝั่ง client รู้จักแค่ gateway ตัวเดียว
  • Aggregation (การรวมผลลัพธ์) — สำหรับคำขอที่ต้องการข้อมูลจากหลายบริการ gateway สามารถกระจายออกไป เรียกแต่ละบริการ และประกอบเป็นการตอบกลับเดียว ดังนั้น client จึงเรียกครั้งเดียวแทนหลายครั้ง
  • การรับภาระ cross-cutting concerns — การยืนยันตัวตน การอนุญาต การ terminate TLS การจำกัดอัตรา การ logging คำขอ และการ caching ทั้งหมดเกิดขึ้นครั้งเดียวที่ขอบ แทนที่จะถูกทำซ้ำในทุกบริการ
flowchart LR
  Web[Web Client] --> GW
  Mobile[Mobile Client] --> GW
  ThirdParty[Third-party API client] --> GW
  GW[API Gateway<br/>routing - aggregation - auth - rate limit]
  GW --> O[Order Service]
  GW --> I[Inventory Service]
  GW --> C[Customer Service]
API Gateway — จุดเข้าจุดเดียวที่จัดเส้นทาง รวมผลลัพธ์ และรับภาระ cross-cutting concerns

พฤติกรรมของ gateway เป็น configuration ไม่ใช่ code แอปพลิเคชัน คุณแค่ประกาศ route กับนโยบายที่จะใช้ แล้วเอนจินของ gateway บังคับใช้ให้เอง ตัวอย่างข้างล่างคือ configuration ที่จับคู่ path สาธารณะเข้ากับ service ภายใน พร้อมแนบการยืนยันตัวตนและ rate limit ให้แต่ละ route

# api-gateway.yaml — routes external paths to internal services
listen: ":443"
tls:
cert: /etc/gateway/tls.crt
key: /etc/gateway/tls.key
routes:
- path: /api/orders/*
upstream: http://order-service
methods: [GET, POST]
auth:
type: jwt
issuer: https://auth.example.com
rate_limit:
requests_per_minute: 120
- path: /api/customers/*
upstream: http://customer-service
methods: [GET]
auth:
type: jwt
issuer: https://auth.example.com
rate_limit:
requests_per_minute: 60
# An aggregating route: the gateway fans out and composes one response.
- path: /api/order-summary/:id
aggregate:
- get: http://order-service/orders/:id # as "order"
as: order
- get: http://customer-service/customers/:customerId
as: customer
- get: http://inventory-service/stock/:sku
as: stock
auth:
type: jwt
issuer: https://auth.example.com

สิ่งที่คุณได้:

  • จุดเข้าที่เรียบง่ายจุดเดียว client รู้จัก host เดียวและ API สาธารณะที่เสถียร บริการภายในสามารถถูกแยก รวม เปลี่ยนชื่อ หรือย้ายได้โดยไม่ทำให้ client ตัวใดพัง
  • cross-cutting concerns ในที่เดียว การยืนยันตัวตน TLS การจำกัดอัตรา และการ logging ถูกตั้งค่าครั้งเดียวที่ขอบ แทนที่จะถูก implement ใหม่ในทุกบริการ
  • วิ่งไปกลับน้อยลง การรวมผลลัพธ์ช่วยให้ client เติมข้อมูลทั้งหน้าจอได้ด้วยคำขอเดียว ซึ่งสำคัญที่สุดบนการเชื่อมต่อมือถือที่มี latency สูง

สิ่งที่คุณต้องจ่าย:

  • มี component ใหม่ที่ต้องสร้างและดูแล ตัว gateway เองต้อง available สูงและ scale ได้ เพราะยืนอยู่บนเส้นทางวิกฤตของทุก request ถ้า gateway ล่ม ระบบก็ล่มทั้งระบบ
  • เสี่ยงกลายเป็นคอขวดหรือ God-object พอกฎ routing การแปลงข้อมูล และการรวมผลลัพธ์สะสมมากขึ้น gateway ก็โตเป็น component ใหญ่ที่ทุกทีมต้องเข้ามาแก้ กลายเป็นคอขวดของการพัฒนา เพราะการเปลี่ยนแปลงของทีมหนึ่งต้องเข้าคิวรออีกทีม
  • ความเสี่ยงของการทำให้ทั่วไปเกินไป gateway เดียวที่พยายามให้บริการ web app, mobile app และ third-party integration ให้ดีเท่ากันทั้งหมด มักให้บริการไม่ดีกับใครเลย เพราะความต้องการของแต่ละฝ่ายต่างกัน

ต้นทุนข้อสุดท้ายนั้นคือแรงจูงใจของ pattern ถัดไป: แทนที่จะมี gateway เดียวสำหรับทุกคน ให้ client แต่ละชนิดมี gateway ของตัวเอง ดู Backends for Frontends

  • Backends for Frontends — gateway ที่ปรับแต่งหนึ่งตัวต่อ client หนึ่งชนิด แก้ปัญหาความเสี่ยงของ God-object
  • Service Discovery — วิธีที่ gateway ค้นหา instance ที่ยังมีชีวิตของ service ปลายทาง
  • API Composition — เทคนิคการรวมผลลัพธ์ที่ gateway ใช้เพื่อรวมข้อมูลจากหลายบริการ
ข้อดีข้อแลกเปลี่ยน
client ติดต่อระบบผ่านจุดเดียว ง่ายต่อการ managesingle point of failure ถ้า gateway ล่ม ทั้งระบบหยุด
cross-cutting concerns (auth, rate limit) ทำครั้งเดียวเพิ่ม latency ทุก request ต้องผ่าน gateway
service ภายในเปลี่ยนได้โดย client ไม่รู้gateway อาจกลายเป็น bottleneck ภายใต้ load สูง
รองรับ aggregation — client call ครั้งเดียวได้หลาย serviceต้องดูแล gateway แยกต่างหาก เพิ่ม operational complexity

God Gateway — gateway ที่มี business logic ฝังอยู่แทนที่จะเป็นแค่ routing/auth

อาการ:

  • gateway มี if/else ตาม business rule
  • เปลี่ยน business logic ต้องแก้ gateway
  • gateway รู้รายละเอียด domain มากกว่า service เจ้าของ

Gateway Coupling — service ทุกตัว deploy ได้ก็ต่อเมื่อ gateway พร้อม

อาการ:

  • release schedule ของ gateway block release ของ service
  • ทีมหลายทีมรอ gateway team อยู่เสมอ

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

Netflix:

  • ใช้ Zuul (ต่อมาเป็น Zuul 2) เป็น API Gateway รับ request หลายพันล้านครั้งต่อวัน
  • handle authentication, routing, และ A/B testing ที่ gateway layer
  • ต่อมาย้ายไปใช้ GraphQL federation สำหรับ aggregation ที่ซับซ้อน

Amazon:

  • แต่ละ product team มี API Gateway ของตัวเอง
  • ไม่มี central gateway เดียว — แบ่งตาม domain ownership
จุดประสงค์หลักของ API Gateway คืออะไร?
การรวมคำขอ (aggregation) ช่วย mobile client อย่างไร?
cross-cutting concern ใดเหมาะกับ gateway โดยธรรมชาติ?
ความเสี่ยงสำคัญของ API Gateway เดียวคืออะไร?