สรุปสั้นๆ (TL;DR)
ชื่อทรัพยากรของ REST API ควรกำหนดเป็นพหูพจน์ ใช้ /pets/{id} ไม่ใช่ /pet/{id}. การใช้ชื่อพหูพจน์ช่วยให้การแสดงคอลเลกชันเป็นไปอย่างสอดคล้องกัน, สอดคล้องกับความหมายของ HTTP, และตรงกับวิธีที่นักพัฒนาคิดเกี่ยวกับทรัพยากร Modern PetstoreAPI ใช้ชื่อพหูพจน์ตลอดการออกแบบ API ของตน โดยปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดของอุตสาหกรรม
บทนำ
คุณกำลังออกแบบ REST API คุณต้องการ endpoint เพื่อดึงข้อมูลผู้ใช้ด้วย ID คุณจะใช้ /user/123 หรือ /users/123? คำถามนี้ได้จุดชนวนให้เกิดการถกเถียงนับครั้งไม่ถ้วน ทั้งใน Stack Overflow และการโต้แย้งภายในทีม
คำตอบที่ชัดเจนคือ: ใช้รูปแบบพหูพจน์ แต่การทำความเข้าใจว่าทำไมจึงสำคัญกว่าการจำกฎเพียงอย่างเดียว เหตุผลเชื่อมโยงกับวิธีการทำงานของ REST, พฤติกรรมของคอลเลกชัน, และวิธีที่นักพัฒนาคิดเกี่ยวกับทรัพยากร
Swagger Petstore แบบเก่าทำผิดพลาด โดยใช้ /pet/{id} แทน /pets/{id} ความไม่สอดคล้องกันนี้ได้สอนรูปแบบที่ผิดให้กับนักพัฒนาหลายล้านคน Modern PetstoreAPI แก้ไขปัญหานี้โดยใช้ชื่อพหูพจน์อย่างสอดคล้องกันในทุก endpoint
ในคู่มือนี้ คุณจะได้เรียนรู้ว่าทำไมชื่อพหูพจน์จึงเป็นทางเลือกที่ถูกต้อง, สอดคล้องกับหลักการของ REST อย่างไร, และวิธีการนำไปใช้อย่างถูกต้องโดยใช้ Modern PetstoreAPI เป็นข้อมูลอ้างอิง
การถกเถียงเรื่องพหูพจน์และเอกพจน์
การถกเถียงนี้มีอยู่จริง เพราะทั้งสองแนวทางดูเหมือนมีเหตุผลเมื่อมองแวบแรก
ข้อโต้แย้งแบบเอกพจน์
"เมื่อฉันร้องขอ /user/123 ฉันได้ผู้ใช้หนึ่งคน ไม่ใช่ผู้ใช้หลายคน เอกพจน์จึงสมเหตุสมผล"
เหตุผลนี้มุ่งเน้นไปที่การตอบสนอง—คุณกำลังได้ทรัพยากรเดียว ดังนั้น URL ควรกำหนดเป็นเอกพจน์
ข้อโต้แย้งแบบพหูพจน์
"URL แสดงถึงคอลเลกชัน /users คือคอลเลกชันของผู้ใช้ทั้งหมด /users/123 คือรายการที่ 123 ในคอลเลกชันนั้น"
เหตุผลนี้มุ่งเน้นไปที่โครงสร้างทรัพยากร—URL แสดงถึงคอลเลกชัน และคุณกำลังเข้าถึงรายการภายในคอลเลกชันเหล่านั้น
ทำไมเรื่องนี้ถึงสำคัญ
ทางเลือกของคุณส่งผลต่อ:
- ความสอดคล้องของ API - การตั้งชื่อที่ผสมกันทำให้ผู้พัฒนาสับสน
- แบบจำลองทางความคิด (Mental models) - วิธีที่นักพัฒนาเข้าใจโครงสร้าง API ของคุณ
- การสร้างโค้ด (Code generation) - เครื่องมือสร้างโค้ดไคลเอ็นต์ตามชื่อทรัพยากร
- ความชัดเจนของเอกสาร (Documentation clarity) - เอกสารต้องอธิบายตรรกะการตั้งชื่อของคุณ
ทำไมชื่อพหูพจน์ถึงเป็นฝ่ายชนะ
ชื่อทรัพยากรแบบพหูพจน์สอดคล้องกับหลักการของ REST และความหมายของ HTTP นี่คือเหตุผล
1. คอลเลกชันเป็นพหูพจน์
ใน REST ทรัพยากรคือคอลเลกชัน เมื่อคุณเข้าถึง /users คุณกำลังเข้าถึงคอลเลกชันผู้ใช้ เมื่อคุณเข้าถึง /users/123 คุณกำลังเข้าถึงรายการที่ 123 ในคอลเลกชันผู้ใช้
GET /users ← คอลเลกชันผู้ใช้
GET /users/123 ← รายการที่ 123 ในคอลเลกชันผู้ใช้
POST /users ← เพิ่มเข้าในคอลเลกชันผู้ใช้
DELETE /users/123 ← ลบรายการที่ 123 ออกจากคอลเลกชันผู้ใช้
แบบจำลองทางความคิดนี้มีความสอดคล้องกัน คอลเลกชันมักจะเป็น /users ไม่ว่าคุณจะเข้าถึงรายการทั้งหมดหรือรายการเดียว
ด้วยชื่อเอกพจน์ แบบจำลองทางความคิดจะพังทลายลง:
GET /user ← ผู้ใช้คนไหน?
GET /user/123 ← อันนี้สมเหตุสมผล
POST /user ← เพิ่มเข้า... อะไร?
2. เมธอด HTTP ดำเนินการบนคอลเลกชัน
เมธอด HTTP อธิบายการดำเนินการบนคอลเลกชัน:
GET /users- ดึงข้อมูลคอลเลกชันPOST /users- เพิ่มลงในคอลเลกชันGET /users/123- ดึงข้อมูลรายการที่ 123 จากคอลเลกชันPUT /users/123- แทนที่รายการที่ 123 ในคอลเลกชันDELETE /users/123- ลบรายการที่ 123 ออกจากคอลเลกชัน
คอลเลกชันคือทรัพยากร รายการแต่ละรายการเป็นสมาชิกของคอลเลกชันนั้น
3. ความสอดคล้องในทุก Endpoint
ชื่อพหูพจน์สร้างความสอดคล้อง:
GET /pets ← คอลเลกชัน
GET /pets/123 ← รายการในคอลเลกชัน
GET /orders ← คอลเลกชัน
GET /orders/456 ← รายการในคอลเลกชัน
ชื่อเอกพจน์บังคับให้คุณสลับไปมาระหว่างเอกพจน์และพหูพจน์:
GET /pet ← ไม่สมเหตุสมผล
GET /pet/123 ← สมเหตุสมผล
GET /pets ← เดี๋ยวสิ ตอนนี้เป็นพหูพจน์แล้วเหรอ?
4. มาตรฐานอุตสาหกรรม
API หลักๆ ใช้วิธีการตั้งชื่อแบบพหูพจน์:
- GitHub API:
/repos,/users,/issues - Stripe API:
/customers,/charges,/subscriptions - Twilio API:
/accounts,/messages,/calls - Google APIs:
/users,/groups,/files
Modern PetstoreAPI เป็นไปตามมาตรฐานนี้ด้วย /pets, /orders, /users
แบบจำลองทางความคิดเกี่ยวกับคอลเลกชัน
การทำความเข้าใจคอลเลกชันช่วยให้คุณออกแบบ API ได้ดีขึ้น
คอลเลกชันใน REST
คอลเลกชันคือชุดของทรัพยากร ใน Pet Store API:
/pets- คอลเลกชันของสัตว์เลี้ยงทั้งหมด/orders- คอลเลกชันของคำสั่งซื้อทั้งหมด/users- คอลเลกชันของผู้ใช้ทั้งหมด
แต่ละคอลเลกชันรองรับการดำเนินการ:
GET /pets ← แสดงรายการสัตว์เลี้ยง (พร้อมการกรอง, การแบ่งหน้า)
POST /pets ← สร้างสัตว์เลี้ยงใหม่
GET /pets/{id} ← ดึงข้อมูลสัตว์เลี้ยงที่เฉพาะเจาะจง
PUT /pets/{id} ← อัปเดตสัตว์เลี้ยงที่เฉพาะเจาะจง
DELETE /pets/{id} ← ลบสัตว์เลี้ยงที่เฉพาะเจาะจง
คอลเลกชันย่อย
คอลเลกชันสามารถมีคอลเลกชันย่อยได้:
GET /pets/{id}/photos ← คอลเลกชันรูปภาพสำหรับสัตว์เลี้ยง {id}
POST /pets/{id}/photos ← เพิ่มรูปภาพลงในสัตว์เลี้ยง {id}
GET /pets/{id}/photos/{photoId} ← รูปภาพเฉพาะ
รูปแบบยังคงสอดคล้องกัน: คอลเลกชันเป็นพหูพจน์, รายการจะถูกเข้าถึงด้วย ID
ตัวอย่าง Modern PetstoreAPI
Modern PetstoreAPI นำไปใช้อย่างถูกต้อง:
GET /pets
GET /pets/{petId}
GET /pets/{petId}/photos
POST /pets/{petId}/vaccinations
GET /orders
GET /orders/{orderId}
GET /orders/{orderId}/items
ทุกคอลเลกชันเป็นพหูพจน์ การเข้าถึงแต่ละรายการใช้ {id} ภายในคอลเลกชันนั้น
Modern PetstoreAPI ใช้ชื่อพหูพจน์อย่างไร
มาดูตัวอย่างจริงจาก Modern PetstoreAPI กัน
ทรัพยากรสัตว์เลี้ยง (Pet Resources)
GET /pets ← แสดงรายการสัตว์เลี้ยงทั้งหมด
POST /pets ← สร้างสัตว์เลี้ยงใหม่
GET /pets/{petId} ← ดึงข้อมูลสัตว์เลี้ยงเฉพาะ
PUT /pets/{petId} ← อัปเดตสัตว์เลี้ยง
DELETE /pets/{petId} ← ลบสัตว์เลี้ยง
GET /pets?status=AVAILABLE ← กรองสัตว์เลี้ยงตามสถานะ
ทรัพยากรคำสั่งซื้อ (Order Resources)
GET /orders ← แสดงรายการคำสั่งซื้อทั้งหมด
POST /orders ← สร้างคำสั่งซื้อ
GET /orders/{orderId} ← ดึงข้อมูลคำสั่งซื้อเฉพาะ
PUT /orders/{orderId} ← อัปเดตคำสั่งซื้อ
DELETE /orders/{orderId} ← ยกเลิกคำสั่งซื้อ
ทรัพยากรผู้ใช้ (User Resources)
GET /users ← แสดงรายการผู้ใช้
POST /users ← สร้างผู้ใช้
GET /users/{userId} ← ดึงข้อมูลผู้ใช้เฉพาะ
PUT /users/{userId} ← อัปเดตผู้ใช้
DELETE /users/{userId} ← ลบผู้ใช้
ทรัพยากรแบบซ้อนกัน (Nested Resources)
GET /pets/{petId}/photos ← รูปภาพของสัตว์เลี้ยง
POST /pets/{petId}/photos ← เพิ่มรูปภาพ
GET /pets/{petId}/vaccinations ← การฉีดวัคซีนของสัตว์เลี้ยง
POST /pets/{petId}/vaccinations ← บันทึกการฉีดวัคซีน
GET /orders/{orderId}/items ← รายการคำสั่งซื้อ
ตรวจสอบ เอกสารประกอบ REST API ฉบับเต็ม สำหรับรายการ endpoint ทั้งหมด
ข้อโต้แย้งทั่วไปสำหรับการใช้เอกพจน์ (และทำไมจึงผิด)
มาดูข้อโต้แย้งทั่วไปสำหรับการใช้ชื่อเอกพจน์กัน
ข้อโต้แย้งที่ 1: “การตอบสนองเป็นเอกพจน์”
ข้ออ้าง: “เมื่อฉันใช้ GET /user/123 ฉันได้ผู้ใช้หนึ่งคน เอกพจน์จึงสมเหตุสมผล”
โต้แย้ง: URL แสดงถึงตำแหน่งของทรัพยากร ไม่ใช่การตอบสนอง /users/123 หมายถึง “รายการที่ 123 ในคอลเลกชันผู้ใช้” การที่การตอบสนองเป็นเอกพจน์ไม่ได้เปลี่ยนแปลงโครงสร้างของคอลเลกชัน
ข้อโต้แย้งที่ 2: “อ่านแล้วเข้าใจง่ายกว่าในโค้ด”
ข้ออ้าง: “getUser(id) อ่านแล้วเข้าใจง่ายกว่า getUsers(id)”
โต้แย้ง: การตั้งชื่อโค้ดฝั่งไคลเอ็นต์ของคุณไม่ขึ้นกับโครงสร้าง URL คุณสามารถมี:
// URL: GET /users/123
function getUser(id) {
return api.get(`/users/${id}`);
}
ชื่อฟังก์ชันไม่จำเป็นต้องตรงกับ URL
ข้อโต้แย้งที่ 3: “เอกพจน์ช่วยหลีกเลี่ยงปัญหาไวยากรณ์”
ข้ออ้าง: “แล้วทรัพยากรอย่าง ‘status’ หรือ ‘information’ ที่ไม่มีรูปพหูพจน์ที่ชัดเจนล่ะ?”
โต้แย้ง: โดยทั่วไปแล้วสิ่งเหล่านี้เป็นทรัพยากรแบบ Singleton ไม่ใช่คอลเลกชัน ใช้เอกพจน์สำหรับ Singleton:
GET /status ← สถานะระบบ (singleton)
GET /configuration ← การตั้งค่าแอป (singleton)
GET /users ← คอลเลกชันผู้ใช้ (plural)
ข้อโต้แย้งที่ 4: “ORM ของฉันใช้ชื่อตารางแบบเอกพจน์”
ข้ออ้าง: “ตารางฐานข้อมูลของฉันเป็นเอกพจน์ (user, order) ดังนั้น API ของฉันจึงควรตรงกัน”
โต้แย้ง: การออกแบบ API ของคุณไม่ควรเปิดเผยรายละเอียดการนำไปใช้ของฐานข้อมูล REST API แสดงถึงทรัพยากร ไม่ใช่ตารางฐานข้อมูล ใช้รูปแบบพหูพจน์สำหรับคอลเลกชันโดยไม่คำนึงถึงโครงสร้างฐานข้อมูลของคุณ
การทดสอบการตั้งชื่อทรัพยากรด้วย Apidog
Apidog ช่วยคุณตรวจสอบความถูกต้องและทดสอบข้อตกลงการตั้งชื่อทรัพยากร
นำเข้า Modern PetstoreAPI
- นำเข้า Modern PetstoreAPI OpenAPI spec
- Apidog ตรวจจับรูปแบบทรัพยากรโดยอัตโนมัติ
- ตรวจสอบการตั้งชื่อ endpoint เพื่อความสอดคล้อง
สร้างการทดสอบข้อตกลงการตั้งชื่อ
// Apidog test script
pm.test("Resource names are plural", function() {
const path = pm.request.url.getPath();
const segments = path.split('/').filter(s => s);
// Check first segment is plural
const resource = segments[0];
pm.expect(resource).to.match(/s$/); // Ends with 's'
});
ตรวจสอบความสอดคล้อง
Apidog สามารถตรวจสอบ:
- Endpoint ของคอลเลกชันทั้งหมดใช้ชื่อพหูพจน์
- ทรัพยากรย่อยตามรูปแบบเดียวกัน
- ไม่มีการผสมกันระหว่างเอกพจน์/พหูพจน์ใน API เดียวกัน
ทดสอบด้วยคำขอจริง
GET https://api.petstoreapi.com/v1/pets
GET https://api.petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24
GET https://api.petstoreapi.com/v1/orders
Apidog ตรวจสอบการตอบสนองและช่วยให้คุณมั่นใจในความสอดคล้องของการตั้งชื่อทั่วทั้ง API ของคุณ
กรณีพิเศษและข้อยกเว้น
ทรัพยากรบางอย่างไม่เข้ากับรูปแบบพหูพจน์
ทรัพยากรแบบ Singleton
ทรัพยากรที่มีเพียงครั้งเดียวควรกำหนดเป็นเอกพจน์:
GET /status ← สถานะระบบ
GET /configuration ← การกำหนดค่าแอป
GET /health ← ตรวจสอบสถานะการทำงาน
GET /metrics ← เมตริกของระบบ
สิ่งเหล่านี้ไม่ใช่คอลเลกชัน ดังนั้นจึงไม่ใช้พหูพจน์
ทรัพยากรแบบ Controller
การดำเนินการที่ไม่เข้ากับการดำเนินการ CRUD:
POST /login ← การดำเนินการตรวจสอบสิทธิ์
POST /logout ← การสิ้นสุดเซสชัน
POST /search ← การดำเนินการค้นหาที่ซับซ้อน
สิ่งเหล่านี้เป็นข้อยกเว้นที่ยอมรับได้ เพราะมันแสดงถึงการกระทำ ไม่ใช่ทรัพยากร
คำนามที่นับไม่ได้
คำนามบางคำไม่มีรูปพหูพจน์ที่ชัดเจน:
GET /information ← เอกพจน์ (นับไม่ได้)
GET /data ← เอกพจน์ (นับไม่ได้)
GET /equipment ← เอกพจน์ (นับไม่ได้)
ใช้เอกพจน์สำหรับคำนามที่นับไม่ได้ แต่สิ่งเหล่านี้หายากใน API ทั่วไป
แนวทางของ Modern PetstoreAPI
Modern PetstoreAPI จัดการกรณีเหล่านี้:
# คอลเลกชัน (พหูพจน์)
GET /pets
GET /orders
GET /users
# Singleton (เอกพจน์)
GET /health
GET /metrics
# การกระทำ (คำกริยาเอกพจน์)
POST /login
POST /logout
สรุป
ชื่อทรัพยากรของ REST API ควรกำหนดเป็นพหูพจน์ ซึ่งสอดคล้องกับความหมายของคอลเลกชัน, เมธอด HTTP, และมาตรฐานอุตสาหกรรม Modern PetstoreAPI แสดงให้เห็นรูปแบบนี้อย่างถูกต้องในทุก endpoint
ประเด็นสำคัญ:
- ใช้ชื่อพหูพจน์สำหรับคอลเลกชัน (
/pets,/orders,/users) - รายการแต่ละรายการจะถูกเข้าถึงภายในคอลเลกชัน (
/pets/123) - ทรัพยากรแบบ Singleton สามารถเป็นเอกพจน์ได้ (
/status,/health) - ความสอดคล้องสำคัญกว่าความสมบูรณ์แบบ
- ทดสอบข้อตกลงการตั้งชื่อของคุณด้วย Apidog
การถกเถียงเรื่องพหูพจน์และเอกพจน์สิ้นสุดลงแล้ว ปฏิบัติตามมาตรฐานอุตสาหกรรม ใช้ชื่อพหูพจน์ และสร้าง API ที่นักพัฒนาเข้าใจได้โดยสัญชาตญาณ
ขั้นตอนต่อไป:
- ทบทวน endpoint API ของคุณเพื่อความสอดคล้องของการตั้งชื่อ
- ตรวจสอบ เอกสาร Modern PetstoreAPI สำหรับรูปแบบอ้างอิง
- ใช้ Apidog เพื่อตรวจสอบการออกแบบ API ของคุณ
- อัปเดต OpenAPI spec ของคุณด้วยชื่อทรัพยากรที่เป็นพหูพจน์
คำถามที่พบบ่อย (FAQ)
ฉันควรเปลี่ยน API ที่มีอยู่จากเอกพจน์เป็นพหูพจน์หรือไม่?
หาก API ของคุณใช้งานจริงอยู่แล้ว การเปลี่ยนชื่อทรัพยากรเป็นการเปลี่ยนแปลงที่ส่งผลกระทบต่อระบบ (breaking change) พิจารณา:
- การเพิ่ม endpoint v2 ใหม่ด้วยชื่อพหูพจน์
- การรักษาความเข้ากันได้แบบย้อนหลังด้วยการเปลี่ยนเส้นทาง (redirects)
- การจัดทำเอกสารข้อตกลงการตั้งชื่อให้ชัดเจน
อย่าเปลี่ยนแปลงไคลเอ็นต์ที่มีอยู่เพียงเพื่อความสอดคล้องของการตั้งชื่อเพียงอย่างเดียว
แล้วทรัพยากรที่เป็นพหูพจน์อยู่แล้วล่ะ?
หากชื่อทรัพยากรเป็นพหูพจน์โดยธรรมชาติ (เช่น “analytics” หรือ “series”) ให้คงไว้ตามเดิม:
GET /analytics ← เป็นพหูพจน์อยู่แล้ว
GET /series ← เป็นพหูพจน์อยู่แล้ว
GET /species ← เป็นพหูพจน์อยู่แล้ว
ฉันจะจัดการกับทรัพยากรแบบซ้อนกันได้อย่างไร?
ให้ทั้งสองระดับเป็นพหูพจน์:
GET /users/{userId}/orders ← ทั้งคู่เป็นพหูพจน์
GET /pets/{petId}/vaccinations ← ทั้งคู่เป็นพหูพจน์
GET /orders/{orderId}/items ← ทั้งคู่เป็นพหูพจน์
ถ้าทีมของฉันชอบแบบเอกพจน์ล่ะ?
ความสอดคล้องภายใน API ของคุณสำคัญที่สุด หากทีมของคุณได้กำหนดมาตรฐานเป็นชื่อเอกพจน์แล้ว และการเปลี่ยนแปลงจะทำให้เกิดความสับสน ก็ให้ยึดตามเอกพจน์ต่อไป แต่สำหรับ API ใหม่ ให้ใช้พหูพจน์
GraphQL ใช้พหูพจน์หรือเอกพจน์?
GraphQL โดยทั่วไปจะใช้เอกพจน์สำหรับการสืบค้นรายการเดียว และพหูพจน์สำหรับรายการ:
query {
user(id: "123") { ... } # เอกพจน์
users(limit: 10) { ... } # พหูพจน์
}
สิ่งนี้แตกต่างจาก REST เนื่องจาก GraphQL queries จะระบุอย่างชัดเจนว่าต้องการคืนค่าหนึ่งรายการหรือหลายรายการ
Modern PetstoreAPI จัดการเรื่องนี้อย่างไร?
Modern PetstoreAPI ใช้ชื่อพหูพจน์อย่างสอดคล้องกันในทุก REST endpoint ตรวจสอบ คู่มือ REST API สำหรับตัวอย่างที่สมบูรณ์
ฉันสามารถทดสอบข้อตกลงการตั้งชื่อโดยอัตโนมัติได้หรือไม่?
ได้, Apidog สามารถรันการทดสอบอัตโนมัติเพื่อตรวจสอบรูปแบบการตั้งชื่อทรัพยากรทั่วทั้ง API ของคุณได้ นำเข้า OpenAPI spec ของคุณและสร้าง test case สำหรับข้อตกลงการตั้งชื่อ
แล้ว API ที่ไม่ใช่ภาษาอังกฤษล่ะ?
กฎพหูพจน์สามารถนำไปใช้ได้โดยไม่คำนึงถึงภาษา หาก API ของคุณใช้ชื่อทรัพยากรภาษาฝรั่งเศส ให้ใช้รูปพหูพจน์ภาษาฝรั่งเศส หากใช้ภาษาญี่ปุ่น ให้ปฏิบัติตามกฎไวยากรณ์ภาษาญี่ปุ่น หลักการ (คอลเลกชันเป็นพหูพจน์) ยังคงเหมือนเดิม
