การออกแบบ API — สัญญาที่ดีระหว่างระบบ
Resource Modeling · HTTP Methods · Pagination · Versioning · ▶ ในคลิป 0:29:50 – 0:46:06
API คือ สัญญา (contract) ระหว่างระบบของเรากับโลกภายนอก — และสัญญาที่ปล่อยออกไปแล้ว แก้ยากที่สุด เพราะมี client นับพันยึดตามอยู่ API ที่ดีจึงต้อง "เดาได้" (predictable): นักพัฒนาที่เห็น endpoint หนึ่งตัว ควรเดา endpoint ที่เหลือได้ถูกเกือบหมด
4.1 คิดเป็น Resource ไม่ใช่ Action
หัวใจของ REST-style API: URL แทน "สิ่งของ" (คำนาม) ส่วน "การกระทำ" (กริยา) ให้ HTTP method เป็นคนบอก — อย่าฝังกริยาลงใน URL
4.2 HTTP Methods และคุณสมบัติ Idempotency
Idempotent = เรียกซ้ำกี่ครั้งผลลัพธ์สุดท้ายเหมือนเรียกครั้งเดียว — สำคัญมากเพราะ network ล้มเหลวได้เสมอ และ client ต้องรู้ว่า method ไหน "retry ได้อย่างปลอดภัย"
| Method | ความหมาย | Idempotent? | ตัวอย่าง |
|---|---|---|---|
| GET | อ่านข้อมูล ไม่เปลี่ยนแปลงอะไร | ✓ (และ safe) | GET /orders/90 |
| POST | สร้างใหม่ / สั่งประมวลผล | ✗ — เรียกซ้ำ = สร้างซ้ำ | POST /orders |
| PUT | แทนที่ทั้งก้อนด้วยข้อมูลใหม่ | ✓ | PUT /users/42 |
| PATCH | แก้บางฟิลด์ | ขึ้นกับการออกแบบ | PATCH /users/42 |
| DELETE | ลบ | ✓ (ลบซ้ำ = ก็ยังถูกลบอยู่ดี) | DELETE /orders/90 |
🧭 Idempotency Key — เทคนิคระดับ production — POST ไม่ idempotent โดยธรรมชาติ แต่ระบบจ่ายเงินยอม "ตัดบัตรซ้ำ" ไม่ได้ — ทางแก้คือให้ client แนบ
Idempotency-Key: <uuid>มากับ request ฝั่งเซิร์ฟเวอร์จำ key ที่เคยประมวลผลแล้ว ถ้าเจอซ้ำ (เช่น client retry เพราะ timeout) ให้ตอบผลลัพธ์เดิมแทนการทำงานซ้ำ — Stripe ใช้แพตเทิร์นนี้กับทุก API ที่สร้างธุรกรรม
4.3 Status Codes — ตอบให้ตรงความหมาย
- 2xx สำเร็จ —
200 OK,201 Created(สร้างสำเร็จ + ส่ง location กลับ),204 No Content(ลบสำเร็จ ไม่มี body) - 4xx client ผิด —
400 Bad Request(payload ผิดรูป),401 Unauthorized(ยังไม่พิสูจน์ตัวตน),403 Forbidden(ตัวตนชัดแต่ไม่มีสิทธิ์),404 Not Found,429 Too Many Requests(โดน rate limit — บทที่ 8) - 5xx server ผิด —
500 Internal Server Error,503 Service Unavailable(ล่มชั่วคราว/ปิดปรับปรุง)
ข้อผิดพลาดควรมี body ที่ machine-readable และบอกวิธีแก้ ไม่ใช่แค่ข้อความลอย ๆ:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "email ไม่ใช่รูปแบบที่ถูกต้อง",
"field": "email",
"request_id": "req_8f3ka" // ไว้ตามรอยใน log
}
}4.4 Pagination — อย่าส่งข้อมูลล้านแถวในครั้งเดียว
ทุก endpoint ที่คืน list ต้องแบ่งหน้า มีสองแนวทางหลักที่ trade-off ต่างกันชัดเจน:
4.5 Versioning — เผื่อทางให้อนาคต
สักวันเราต้องเปลี่ยนสัญญาแบบที่ client เก่าพัง (breaking change) เช่น เปลี่ยนชื่อฟิลด์ หรือโครงสร้าง response — versioning คือทางหนีไฟ มี 3 สไตล์:
| วิธี | หน้าตา | ข้อสังเกต |
|---|---|---|
| URI path | /v1/orders → /v2/orders | ชัดเจน เห็นด้วยตา cache ง่าย — นิยมที่สุด |
| Header | Accept: application/vnd.api+json;version=2 | URL สะอาด แต่ debug ยากกว่า มองไม่เห็นใน browser |
| Query param | /orders?version=2 | ง่ายสุดแต่ปนกับ param อื่น ไม่ค่อยแนะนำ |
กฎที่สำคัญกว่าการเลือกวิธี: เปลี่ยนแบบไม่พังได้ ไม่ต้องออกเวอร์ชันใหม่ — การ "เพิ่มฟิลด์ใหม่" ใน response ไม่ใช่ breaking change (client ที่ดีต้องเมินฟิลด์ที่ไม่รู้จัก) ออก v2 เฉพาะเมื่อจำเป็นจริง เพราะทุกเวอร์ชันที่เปิดอยู่คือภาระดูแล ×2
✅ สรุปบทที่ 4 — URL คือคำนาม method คือกริยา · จำตาราง idempotency ให้ขึ้นใจ — มันคือเหตุผลว่าอะไร retry ได้ · ใช้ status code ให้ตรงความหมาย + error body ที่บอกวิธีแก้ · list ทุกตัวต้องแบ่งหน้า: เลขหน้า→offset, feed→cursor · เพิ่มฟิลด์ได้เสมอ แต่เปลี่ยน/ลบต้องออกเวอร์ชัน