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]
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”พฤติกรรมของ gateway เป็น configuration ไม่ใช่ code แอปพลิเคชัน คุณแค่ประกาศ route กับนโยบายที่จะใช้ แล้วเอนจินของ gateway บังคับใช้ให้เอง ตัวอย่างข้างล่างคือ configuration ที่จับคู่ path สาธารณะเข้ากับ service ภายใน พร้อมแนบการยืนยันตัวตนและ rate limit ให้แต่ละ route
# api-gateway.yaml — routes external paths to internal serviceslisten: ":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 ติดต่อระบบผ่านจุดเดียว ง่ายต่อการ manage | single 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