API Versioning, Docs, Real-Time & Microservices
11.2 API Versioning, OpenAPI & Real-Time
การจัดการวงจรชีวิต API และรูปแบบการสื่อสารยุคใหม่
1. การทำ Versioning (URL vs Header Accept)
ทฤษฎีและกลไกการทำงาน
API Versioning ช่วยป้องกันไม่ให้ระบบของ Client เดิมพังเวลาที่เราอัปเดต API หรือเปลี่ยนโครงสร้างข้อมูล (Breaking changes)
- URL Versioning: ง่ายและชัดเจนที่สุด (เช่น `/api/v1/users`) ทำ Caching และ Routing ง่าย แต่อาจจะขัดหลัก REST นิดหน่อย (เพราะเลขเวอร์ชันไม่ใช่ตัว Resource)
- Header/Accept Versioning (Content Negotiation): ใช้ HTTP header (เช่น `Accept: application/vnd.company.v1+json`) ถูกต้องตามหลักการ REST มากกว่า, URL สะอาด แต่เทสต์ผ่านเบราว์เซอร์ตรงๆ ยากและตั้งค่าแคชซับซ้อนกว่า
ตัวอย่างโค้ดหรือการตั้งค่า
# FastAPI - ตัวอย่าง URL Versioning เทียบกับ Header Versioning
from fastapi import FastAPI, Header, HTTPException
app = FastAPI()
# 1. URL Versioning
@app.get("/api/v1/products")
def get_products_v1():
return [{"id": 1, "name": "Laptop", "price": 999.00}]
@app.get("/api/v2/products")
def get_products_v2():
# v2 มีการเปลี่ยนโครงสร้าง price
return [{"id": 1, "name": "Laptop", "price": {"amount": 999, "currency": "USD"}}]
# 2. Header Versioning
@app.get("/api/products")
def get_products(accept: str = Header(None)):
if accept == "application/vnd.myapi.v2+json":
return get_products_v2()
return get_products_v1()
Use Case ในชีวิตจริง
Stripe เป็นตัวอย่างที่ดีเยี่ยมของการใช้ Header Versioning แบบอิงตามวันที่ (เช่น `Stripe-Version: 2023-10-16`). วิธีนี้ช่วยให้พวกเขาปล่อยอัปเดตได้ตลอดเวลาโดยที่ URL ไม่เปลี่ยน เมื่อผู้ใช้สมัครแอคเคาท์ Stripe เวอร์ชันของ API จะถูกล็อคตามวันที่สมัครเพื่อความเสถียร ในขณะที่ระบบหลังบ้านของ Stripe จะมีกลไกแปลงข้อมูลให้ตรงตามเวอร์ชันที่ Client ร้องขอแบบไดนามิก
ข้อควรระวังและวิธีแก้
- ข้อควรระวัง: การขึ้นเวอร์ชันใหม่พร่ำเพรื่อ (v2, v3, v4 ถี่เกินไป) ทำให้เกิดหนี้เทคนิค (Technical debt) และบีบให้ฝั่ง Client ต้องคอยตามอัปเดตตลอดเวลา
- วิธีแก้: ควรขึ้นเวอร์ชันหลักเฉพาะตอนที่มี Breaking changes (เช่น เปลี่ยน Type ของฟิลด์, ลบฟิลด์ทิ้ง) เท่านั้น. ให้พยายามอัปเดตแบบ Additive (เพิ่มฟิลด์ใหม่แต่ไม่กระทบของเดิม) ซึ่งไม่จำเป็นต้องเปลี่ยนเวอร์ชัน
2. OpenAPI/Swagger Spec และ Postman Collections
ทฤษฎีและกลไกการทำงาน
- OpenAPI Specification (OAS): มาตรฐานการอธิบายโครงสร้าง REST API (ด้วย JSON หรือ YAML) เพื่อให้ทั้งคนและคอมพิวเตอร์สามารถเข้าใจความสามารถของ API ได้โดยไม่ต้องไปเปิดดูโค้ด
- เครื่องมืออย่าง Swagger UI สามารถแปลง OAS ให้ออกมาเป็นหน้าเว็บ Document แบบ Interactive (กดทดสอบยิงได้) อัตโนมัติ
- Postman Collections: เอกสาร API ที่รันได้จริง นิยมใช้แชร์กันในทีม สามารถสลับ Environment ได้ง่าย และใช้ทำ Automated CI/CD Testing ได้ด้วย
ตัวอย่างโค้ดหรือการตั้งค่า
# ตัวอย่างไฟล์ openapi.yaml
openapi: 3.0.0
info:
title: Users API
version: 1.0.0
paths:
/users/{id}:
get:
summary: ดึงข้อมูลผู้ใช้ตาม ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: ตอบกลับสำเร็จ
content:
application/json:
schema:
type: object
properties:
name:
type: string
Use Case ในชีวิตจริง
ในการทำงานแบบ Design-first API, ทีม Architect จะเขียนไฟล์ OpenAPI YAML ขึ้นมาก่อน. จากนั้นทีม Frontend สามารถใช้เครื่องมือ (เช่น openapi-generator) เพื่อ Generate โค้ด TypeScript SDK เอาไปใช้ต่อได้ทันที. ส่วนทีม Backend ก็ใช้ไฟล์เดียวกันไปสร้าง Route. ในภายหลังทีม DevOps ก็นำไฟล์นี้ไป Import เข้า AWS API Gateway เพื่อตั้งค่า Routing/Validation อัตโนมัติ จบในที่เดียว
ข้อควรระวังและวิธีแก้
- ข้อควรระวัง: ปัญหา Documentation drift คือการที่คนเขียนโค้ดอัปเดต API ไปแล้ว แต่ลืมมาแก้ไฟล์เอกสาร ทำให้คนเรียกใช้เข้าใจผิด
- วิธีแก้: ให้ใช้แนวทาง Code-First โดยใช้ Framework (เช่น FastAPI ใน Python หรือ NestJS Swagger ใน Node.js) ที่จะ Generate เอกสาร OpenAPI JSON ออกมาจาก Type ของโค้ดเราโดยตรงโดยอัตโนมัติ
3. Real-Time (Webhooks vs WebSockets)
ทฤษฎีและกลไกการทำงาน
REST แบบดั้งเดิมต้องใช้การ Polling (Client ทักไปถามบ่อยๆ ว่าเสร็จยัง?) แต่กระบวนทัศน์แบบ Real-time คือการดัน (Push) ข้อมูลไปให้เลย
- Webhooks: การ Push ข้อมูลแบบ Server-to-Server. ระบบต้นทางจะยิง HTTP POST มาที่ URL (Callback) ของเราเมื่อมีเหตุการณ์เกิดขึ้น. (สื่อสารทางเดียว)
- WebSockets (WS): การเชื่อมต่อแบบ TCP ที่เปิดค้างไว้ตลอด คุยได้สองทาง (Bidirectional) ระหว่าง Client และ Server. เหมาะสำหรับแอปแชท, กระดานเทรดหุ้น, หน้าจอ Dashboard สด
- Server-Sent Events (SSE): การเชื่อมต่อผ่าน HTTP มาตรฐานแบบส่งทางเดียว (Server ไป Client) ตั้งค่าง่ายกว่า WS เหมาะกับกรณีที่ Client แค่รอรับข้อมูลฝั่งเดียว
ตัวอย่างโค้ดหรือการตั้งค่า
// ตัวอย่าง WebSocket Server (Node.js/ws)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
// รับข้อความจาก Client
ws.on('message', function incoming(message) {
console.log('received: %s', message);
// กระจายข้อความให้ทุก Client (Broadcast)
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(`User says: ${message}`);
}
});
});
ws.send('Connected to Live Chat Server');
});
Use Case ในชีวิตจริง
แอปสั่งอาหาร Delivery ใช้ WebSockets เพื่อแสดงตำแหน่งรถมอเตอร์ไซค์บนแผนที่แบบลื่นไหล (ต้องสื่อสารตลอดเวลาต่อเนื่อง). แต่เมื่อลูกค้าตัดบัตรเครดิตสำเร็จ, Payment Gateway จะใช้ Webhook ยิงแจ้งเตือนมาที่เซิร์ฟเวอร์หลังบ้านของเรา เพราะมันเป็นแค่การแจ้งเตือนเหตุการณ์ครั้งเดียว (One-off event) แบบหลบฉาก (Asynchronous) ระหว่างเซิร์ฟเวอร์ด้วยกัน
ข้อควรระวังและวิธีแก้
- ข้อควรระวัง (Webhooks): การโจมตีแบบ Replay attacks หรือการที่ Event สูญหายเพราะเซิร์ฟเวอร์เราล่มพอดีจังหวะนั้น
- วิธีแก้: ต้องมีการทำ Signature verification (HMAC) เพื่อยืนยันว่า Webhook มาจากของจริง และสร้างระบบคิว (Queue) ไว้รับข้อมูลที่สามารถรันซ้ำได้อย่างปลอดภัย (Idempotent)
- ข้อควรระวัง (WebSockets): การหลุดของการเชื่อมต่อ และการ Scale เซิร์ฟเวอร์หลายตัวเพื่อรับโหลด (Stateful connection)
- วิธีแก้: ใช้ Redis Pub/Sub เป็น Backplane เพื่อกระจายข้อความข้ามเซิร์ฟเวอร์ และต้องเขียนโค้ดฝั่ง Client ให้มีการ Auto-reconnect เสมอ
4. สถาปัตยกรรม Microservices & API Gateway
ทฤษฎีและกลไกการทำงาน
เมื่อระบบพัฒนาจาก Monolith ไปเป็น Microservices คงไม่ดีแน่ถ้า Client ต้องมานั่งจำ URL ของบริการย่อยๆ 50 ตัว
API Gateway คือด่านหน้าที่คั่นกลางระหว่าง Clients และ Microservices ทำหน้าที่คล้าย Reverse Proxy โดยดูแลเรื่อง:
1. Request Routing (เช่น ชี้ `/users` ไปที่ User Service, `/billing` ไปที่ Billing Service)
2. งานจัดการส่วนกลาง (Cross-cutting concerns) เช่น เข้ารหัส SSL, จัดการ Rate Limiting, และตรวจสอบ Authentication (เช็ค JWT)
3. แปลงรูปแบบข้อมูล (เช่น รับคำสั่ง REST จากแอปมือถือมาแปลเป็น gRPC ส่งให้หลังบ้าน)
ตัวอย่างโค้ดหรือการตั้งค่า
# ตัวอย่างการตั้งค่า NGINX สำหรับทำ API Gateway Routing เบื้องต้น
http {
upstream user_service { server 10.0.1.5:3000; }
upstream order_service { server 10.0.1.6:4000; }
server {
listen 80;
# ตรวจสอบ Auth ที่จุดเดียว
auth_request /validate_token;
location /api/users/ {
proxy_pass http://user_service;
}
location /api/orders/ {
proxy_pass http://order_service;
}
}
}
Use Case ในชีวิตจริง
แอปมือถือของ Uber ยิง API คุยกับ Edge API Gateway เพียงจุดเดียว. Gateway จะเช็ค Token ของผู้ใช้ก่อนเลย เมื่อผ่านแล้ว มันจะส่ง API 'เรียกรถ' ต่อไปยัง Dispatch Service และส่ง API 'ประเมินราคา' ไปที่ Pricing Service. ทีมทำแอปมือถือไม่จำเป็นต้องรู้เลยว่าหลังบ้านมี Microservice กี่สิบตัว และแต่ละตัวใช้ IP อะไร
ข้อควรระวังและวิธีแก้
- ข้อควรระวัง: API Gateway กลายเป็นจุดคอขวดของระบบ (Single Point of Failure) และคอขวดของการพัฒนาถ้าทุกคนต้องมาแก้ไฟล์ Config ไฟล์เดียวกัน
- วิธีแก้: ต้อง Deploy API Gateway แบบเป็น Cluster (High Availability) และใช้ระบบ CI/CD (เช่น GitOps) เพื่อให้แต่ละทีมย่อยสามารถ Register Route ของตัวเองได้แบบอัตโนมัติไม่ชนกัน
🛠️ Weekend Sandbox Challenge
สร้าง Mock API สำหรับระบบ Live-Commerce
- เขียนไฟล์เอกสาร OpenAPI สำหรับ Endpoint `/api/v1/bids` พร้อมกำหนด Schema.
- สร้าง API ตัวนี้ขึ้นมาด้วย Framework ที่สามารถ Generate Swagger UI ได้อัตโนมัติ (เช่น FastAPI, NestJS)
- เพิ่ม WebSocket Endpoint ให้ Client ต่อเข้ามาเพื่อรับการแจ้งเตือน "มีคนประมูลใหม่" แบบสดๆ
- ตั้งค่า Reverse Proxy (เช่น Caddy หรือ Nginx) ให้ทำหน้าที่เป็น API Gateway รับจบที่เดียว โดยส่ง `/api` ไปที่ REST server และส่ง `/ws` ไปที่ WebSocket server
เป้าหมาย: เพื่อสัมผัสประสบการณ์ End-to-End ตั้งแต่การออกแบบ, ทำเอกสาร, ไปจนถึงการวาง Routing สถาปัตยกรรมแบบเรียลไทม์ยุคใหม่
💼 Senior Technical Interview Q&A
Q: คุณจะมีวิธีจัดการกับ Breaking changes ใน API ที่มี Client นับพันกำลังใช้งานอยู่อย่างไร?
A: อย่างแรก ผมจะพยายามหลีกเลี่ยง Breaking changes โดยใช้การอัปเดตแบบ Additive (เช่น เพิ่มฟิลด์ใหม่แต่คงของเดิมไว้). แต่ถ้าเลี่ยงไม่ได้ ผมจะเปิด API เวอร์ชันใหม่ (v2). จากนั้นผมจะติดป้ายประกาศให้ v1 เป็น Deprecated โดยแจ้งเตือนผ่าน HTTP Headers (เช่น `Deprecation: true`) และอัปเดตใน Document. หลังจากนั้นจะคอยมอนิเตอร์ Log ดูว่ายังมีใครเรียก v1 อยู่ไหม พร้อมประกาศไทม์ไลน์การยกเลิกให้บริการ (Sunset timeline) เช่น 6 เดือน. ผมจะปิด v1 ก็ต่อเมื่อทราฟฟิกเป็นศูนย์ หรือถึงกำหนดเส้นตายเท่านั้น
Q: ในสถาปัตยกรรม Microservices ความแตกต่างระหว่าง API Gateway กับ Service Mesh (เช่น Istio) คืออะไร?
A: API Gateway จะจัดการทราฟฟิกแบบ "North-South" (คือ Client จากภายนอกวิ่งเข้ามาใน Cluster) หน้าที่หลักคือ Routing ออกนอกระบบ, จัดการ Authentication และ Rate limiting. ส่วน Service Mesh จะจัดการทราฟฟิกแบบ "East-West" (คือ Microservices คุยกันเองภายใน Cluster) มันใช้ Sidecar proxies แปะไปกับทุก Service เพื่อทำหน้าที่ดูแลเรื่องการเข้ารหัส mTLS, การ Retries ภายใน, Circuit breaking, และ Distributed tracing ระหว่างเซอร์วิสโดยที่โค้ดแอปพลิเคชันไม่ต้องรับรู้