Animated Architecture Patterns

API Gateway

ทางเข้าทางเดียวที่คอยยืนยันตัวตน ทำ rate limit และ route การเรียกไปที่ backend service

หรือที่เรียกกันว่า Edge gateway, API facade

Web browser Mobile iOS · Android Partner B2B system bursts 6 calls API Gateway https://api.example.com Authenticate verify token Rate limit token bucket per client Route by path Also offloaded here: TLS termination · request/response transforms · logging and metrics signature not expired audience then forwards X-User-Id web mobile partner partner 4 tokens · +1 token/s /orders /users /catalog → → → Orders Users Catalog response cache empty GET /catalog → 200 401 Unauthorized 429 Too Many Requests cache miss cache hit private network Orders orders.internal Users users.internal Catalog catalog.internal X-User-Id: 42 not called GET /users GET /orders GET /catalog no token Bearer JWT X-User-Id: 42 401 429 429 200 · 120 ms 200 · 20 ms

ขั้นที่ 1 · ประตูหน้าบ้านบานเดียว client ทั้ง web, mobile และ partner เรียกเข้ามาที่ public endpoint เดียว ทุก request วิ่งผ่าน policy pipeline ชุดเดียวกัน (authenticate, rate limit, route) แล้วถูกส่งต่อตาม path ไปที่ service ที่เป็นเจ้าของ: /orders ไป Orders, /users ไป Users, /catalog ไป Catalog ขากลับ response ก็วิ่งกลับมาทางเดียวกัน ส่วน service ทั้งหมดอยู่ใน private network ทำให้ client ไม่มีทางรู้ว่า service อยู่ที่ไหน หรือแบ่งกันยัง⁠ไง

ขั้นที่ 2 · ยืนยันตัวตนที่ edge gateway ตรวจ credential ก่อนจะ rate limit หรือ route อะไรทั้งนั้น request ไหนไม่มี token หรือ token ไม่ถูกต้อง ก็โดนตีกลับด้วย 401 Unauthorized และไม่มีวันไปถึง backend ส่วนถ้า token ถูกต้อง gateway จะตรวจ signature กับ claim แค่ครั้งเดียว แล้วส่ง identity ของคนที่เรียกต่อไป (ในตัวอย่างนี้ส่งเป็น header X-User-Id) service แต่ละตัวจะได้ไม่ต้องเขียนโค้ดตรวจ token ซ้ำกันเอง

ขั้นที่ 3 · Rate limit แยกตาม client client แต่ละเจ้า (API key หรือ client ID) มี token bucket ของตัวเอง: เรียกหนึ่งครั้งก็ใช้ token ไปหนึ่งตัว และ token จะเติมกลับมาในอัตราคงที่ (ในตัวอย่างนี้สูงสุด 4 ตัว เติม 1 ตัวต่อวินาที) partner ยิงมาเป็น burst จน bucket ของตัวเองหมด การเรียกที่เกินมาเลยได้ 429 Too Many Requests ทางที่ดีควรแนบ header Retry-After มาด้วย ขณะที่การเรียกของ Web ผ่านไปได้ตามปกติ พอ bucket เติมกลับมา partner ก็เรียกได้อีก

ขั้นที่ 4 · ตอบจาก cache GET /catalog ครั้งแรกเป็น cache miss: request วิ่งไปถึง Catalog แล้ว gateway ก็เก็บ response ไว้ตาม time-to-live ครั้งที่เรียกซ้ำเป็น hit คราวนี้ gateway ตอบเองได้ในเวลาแค่เสี้ยวเดียว ไม่ต้องไปแตะ service เลย การ cache เป็นหนึ่งในหลาย cross-cutting concern ที่ gateway รับไปทำแทน service ได้ อย่างอื่นก็มี TLS termination, การแปลง request และ response, logging และ metrics

ปัญหา

พอระบบถูกแบ่งเป็นหลาย service แล้ว ถ้าปล่อยให้ client ทุกตัวเรียก service ทุกตัวได้ตรง ๆ โครงสร้างข้างในก็จะโผล่ออกไปข้างนอก client ทั้ง web, mobile และ partner แต่ละตัวต้องคอยจำรายการ endpoint และทุกครั้งที่ refactor (แยก Orders หรือย้าย Catalog) ก็กลายเป็น breaking change ในแอปของคนอื่น หน้าจอเดียวอาจต้องเรียกหลาย round trip ส่วน service ที่เปิดสู่สาธารณะทุกตัวก็ยังต้องเขียนเรื่องที่ขอบระบบเดิม ๆ ซ้ำเอง (TLS certificate, การตรวจ token, quota ราย client, access log) และแต่ละตัวก็เป็นช่องโจมตีที่หันหน้าออก internet ที่ต้องทำให้แน่นหนาแยกกันเอง

ทำงานยัง⁠ไง

API gateway คือ reverse proxy ระดับ layer 7 ที่กลายเป็นทางเข้าสาธารณะทางเดียว ทุก request ผ่าน pipeline ของ policy ชุดเดียวกันตามลำดับ:

  1. Terminate TLS public certificate อยู่ที่ gateway แล้ว gateway ก็เปิด TLS connection ใหม่ไปหา backend ถ้าให้ดีควรเป็นแบบ mutual
  2. Authenticate ตรวจ bearer token (signature เทียบกับ key ที่ identity provider เผยแพร่ไว้, วันหมดอายุ, issuer และ audience) หรือ API key ถ้าไม่มี credential หรือ credential ไม่ถูกต้องก็ได้ 401 ก่อนจะไปแตะ backend ตัวไหน ส่วน identity ที่ตรวจแล้วก็ถูกส่งต่อไปข้างหลัง
  3. Rate limit หา token bucket ของคนที่เรียก: การเรียกแต่ละครั้งใช้ token หนึ่งตัว และ token เติมกลับในอัตราคงที่ ทำให้ burst ที่ไม่เกินขนาด bucket ผ่านได้ แต่การยิงถล่มต่อเนื่องผ่านไม่ได้ ส่วนที่เกินจะได้ 429 Too Many Requests
  4. Route จับคู่ path, host หรือ header กับ backend แล้วกระจาย load ไปตาม instance ของมัน หรือตอบ request ที่ cache ได้จาก cache ของ gateway เอง
  5. ขากลับ แปลง response ถ้าต้องทำ แล้ว log, วัดการใช้งาน และ trace การเรียกนั้น

การ authenticate ก่อนทำให้ใช้ identity ที่ตรวจแล้วเป็นตัวกำหนด quota กับ cache key ได้ ส่วน limit หยาบ ๆ ราย IP หรือ web application firewall ที่อยู่ข้างหน้าก็ยังรับการยิงถล่มแบบไม่ระบุตัวตนไว้ได้ งานอื่นที่มักย้ายมาไว้ที่นี่คือ CORS, compression และ IP allow list ส่วน request CORS preflight (OPTIONS) ให้ตอบก่อนขั้น authenticate: browser ไม่เคยส่ง credential มากับ preflight และ 401 ตรงนั้นจะบล็อกการเรียกจริง

แบบอื่น ๆ และ pattern ข้างเคียง:

  • Backends for Frontends (BFF) แทนที่จะมี gateway อเนกประสงค์ตัวเดียว ก็รันหนึ่งตัวต่อ client แต่ละประเภท (web, mobile, partner) แต่ละตัวเปิด API แค่ที่ client ของมันต้องใช้พอดี และทีมที่สร้าง client นั้นเป็นเจ้าของ ส่วน edge gateway ที่ใช้ร่วมกันก็ยังรับเรื่อง TLS, authentication และ quota ไว้ข้างหน้าได้
  • Gateway aggregation gateway (หรือ BFF) กระจาย request เดียวออกไปหลาย service แล้วรวมคำตอบเข้าด้วยกัน client ที่ช่างคุยเลยเรียกแค่ round trip เดียวแทนที่จะหลายรอบ
  • Gateway เทียบกับ service mesh gateway ดูแล traffic แบบ north-south ที่มาจาก client เข้าสู่ระบบ: public TLS, API key, quota, versioning ส่วน service mesh ดูแล traffic แบบ east-west ระหว่าง service: mutual TLS, retry, timeout และการย้าย traffic ผ่าน proxy แบบ sidecar หรือระดับ node สองอย่างนี้เสริมกัน และ mesh หลายตัวก็มี ingress gateway มาให้ ที่ใช้เป็น edge แบบง่าย ๆ ได้

ใช้ตอนไหนดี

  • มีหลาย service ที่เปิดให้ client ภายนอกใช้ และอยากได้ endpoint เดียวที่นิ่ง ๆ ทำให้ backend แยก รวม หรือย้ายอยู่ข้างหลังได้โดยไม่ทำให้ใครพัง (facade ในการย้ายแบบ Strangler Fig ก็มักเป็น gateway)
  • client ต่างประเภทต้องใช้ credential, quota หรือ payload ต่างกัน
  • security ที่ขอบระบบและ traffic policy ควรถูกใช้อย่างสม่ำเสมอในที่เดียว โดยทีมที่เป็นเจ้าของเรื่องนี้
  • เปิด API ให้ partner หรือลูกค้าใช้ และต้องมี API key, usage plan และ analytics ราย consumer
  • ไม่ใช่สำหรับ service ตัวเดียวหรือแอปเล็ก ๆ ที่ load balancer หรือ ingress ที่มี TLS ก็พอแล้ว และปกติก็ไม่ควรอยู่บนเส้นทางการเรียกระหว่าง service ที่ service mesh หรือ client library จัดการได้ดีกว่า

ได้อะไร เสียอะไร

  • Single point of failure ทุกการเรียกต้องผ่านมัน availability ของมันเลยเป็นเพดานของทั้งระบบ รัน instance แบบ stateless หลายตัวกระจายหลาย zone อยู่หลัง load balancer, drain connection ตอน deploy และ roll out การเปลี่ยน configuration อย่างระวังเท่ากับโค้ด
  • คอขวดและ hop ที่เพิ่มมา ทุก request ต้องจ่ายค่า network hop อีกหนึ่ง hop บวกกับทุก policy ที่รัน เลยต้อง scale มันแบบ horizontal ทำ policy ให้เบา และทำ load test ด้วย burst ที่ใกล้เคียง production
  • business logic ค่อย ๆ ไหลเข้ามา orchestration, การ map ข้อมูล และ feature rule น่าใส่เข้ามา แต่มันจะเปลี่ยน gateway ให้กลายเป็น monolith ที่ใช้ร่วมกัน ที่ทุกทีมต้องแก้และ redeploy เลยควรให้มันทำแค่ cross-cutting concern ส่วนการปรับรูปข้อมูลเฉพาะ client ควรอยู่ใน BFF หรือ service
  • เป้าที่มีค่าสูง มันถือ public certificate และบังคับ authentication ให้ทุกคน ทำให้มันแน่นหนา ล็อก admin API ของมันไว้ และทำให้ service เข้าถึงไม่ได้ยกเว้นผ่านมัน (private networking, mutual TLS หรือทั้งสองอย่าง) ไม่อย่างนั้นก็อ้อม policy ของมันไปได้ง่าย ๆ
  • identity ที่ส่งต่อมาต้องเชื่อถือได้ service อาจเชื่อ identity header แค่เพราะ gateway เป็นคนใส่ เลยต้องลบ header ชื่อเดียวกันที่ client ส่งมาเองทิ้งที่ขอบ ดีไซน์แบบ zero-trust จะส่ง token ตัวจริงต่อไป หรือแลกเป็น token ที่แคบกว่า แล้วให้แต่ละ service validate ใหม่อีกรอบ
  • cache อย่างระวัง cache เฉพาะ response สาธารณะที่ปลอดภัย โดยใช้ key ที่รวมทุกอย่างที่ทำให้ response เปลี่ยน (path, query, header ที่เกี่ยวข้อง) อย่าส่ง response ของ user คนหนึ่งให้อีกคนจาก cache ที่ใช้ร่วมกันเด็ดขาด และตั้ง TTL ให้สั้น หรือ invalidate เมื่อข้อมูลเปลี่ยน

ข้อควรรู้ตอนลงมือทำ

  • Product: managed service อย่าง Amazon API Gateway, Azure API Management และ Apigee หรือ gateway ที่ host เองอย่าง Kong, Tyk, KrakenD, NGINX, Traefik และ gateway ที่สร้างบน Envoy ส่วนบน Kubernetes ตัว Gateway API (Gateway, HTTPRoute) คือวิธีมาตรฐานในการประกาศ route
  • Token: สำหรับ JWT ให้ cache signing key ของ identity provider (JWKS) แล้วตรวจ signature, algorithm, iss, aud และ exp ในเครื่องเอง ไม่ต้องเรียก IdP ทุก request ส่วน opaque token ต้องเรียก introspection (RFC 7662) เลยควร cache ผลไว้สั้น ๆ ถ้าไม่มี token หรือ token เสีย ให้ตอบ 401 พร้อม header WWW-Authenticate และถ้า token ถูกต้องแต่ไม่มี scope ที่ต้องใช้ ให้ตอบ 403
  • Rate limit: ใช้ key เป็น client ที่ authenticate แล้ว ไม่ใช่แค่ IP address เพราะ NAT กับ mobile network ใช้ address ร่วมกัน และส่ง Retry-After ไปกับ 429 ทุกครั้ง ถ้ามี gateway หลาย instance ก็แชร์ตัวนับกัน (เช่นใน Redis หรือผ่าน global rate-limit service) หรือแบ่ง quota แต่ละก้อนไปตาม instance แล้วยอมรับว่าตัวเลขจะคลาดกันบ้าง
  • Observability: gateway เห็นทุกการเรียก เลยควรให้มันเริ่มหรือส่งต่อ trace context (W3C traceparent) และปล่อย metric ของ rate, error และ latency ราย route ราย client
  • Configuration as code: เก็บ route กับ policy ไว้ใน version control และให้ทีม service เป็นเจ้าของ route ของตัวเองผ่านการ review ไม่ใช่ผ่าน ticket ไปหาทีมกลาง