ในโลกของการพัฒนาแอปพลิเคชันสมัยใหม่ REST API ทำหน้าที่เป็นชั้นสื่อสารพื้นฐาน ช่วยให้ระบบที่แตกต่างกันสามารถแลกเปลี่ยนข้อมูลได้อย่างราบรื่น เมื่อแอปพลิเคชันมีขนาดและความซับซ้อนเพิ่มขึ้น ปริมาณข้อมูลที่จัดการก็เพิ่มขึ้นตามไปด้วย การร้องขอชุดข้อมูลทั้งหมด ซึ่งอาจมีข้อมูลหลายล้านหรือหลายพันล้านรายการ ในการเรียก API เพียงครั้งเดียว เป็นสิ่งที่ไม่ eficiente (ไม่มีประสิทธิภาพ), unreliable (ไม่น่าเชื่อถือ), และเป็น bottleneck (คอขวด) ด้านประสิทธิภาพที่สำคัญ นี่คือจุดที่เทคนิคสำคัญในการออกแบบและพัฒนา API เข้ามามีบทบาท นั่นคือ การแบ่งหน้า REST API (REST API pagination) คู่มือนี้จะให้ภาพรวมที่ลึกซึ้งและครอบคลุมเกี่ยวกับการนำการแบ่งหน้าไปใช้ใน REST API ครอบคลุมทุกอย่างตั้งแต่แนวคิดพื้นฐานไปจนถึงการนำไปใช้จริงขั้นสูงโดยใช้เทคโนโลยี stack ต่างๆ เช่น Node.js, Python และ .NET
ต้องการแพลตฟอร์มแบบครบวงจรที่ช่วยให้ทีมพัฒนาของคุณทำงานร่วมกันได้อย่าง มีประสิทธิภาพสูงสุด?
Apidog ตอบสนองทุกความต้องการของคุณ และ แทนที่ Postman ในราคาที่เข้าถึงได้มากกว่ามาก!
พื้นฐานของการแบ่งหน้า REST API
ก่อนที่จะลงลึกในตัวอย่างโค้ดที่ซับซ้อนและรูปแบบการออกแบบ สิ่งสำคัญคือต้องมีความเข้าใจที่ชัดเจนว่าการแบ่งหน้าคืออะไร และทำไมจึงเป็นสิ่งที่ไม่สามารถละเลยได้ในการออกแบบ API ระดับมืออาชีพ
การแบ่งหน้าใน REST API คืออะไร?
โดยพื้นฐานแล้ว การแบ่งหน้า REST API คือเทคนิคที่ใช้ในการแบ่งการตอบสนองของ REST API endpoint ออกเป็นหน่วยย่อยๆ ที่จัดการได้ง่ายขึ้น ซึ่งมักเรียกว่า "หน้า" แทนที่จะส่งชุดข้อมูลขนาดใหญ่อาจเป็นไปได้ในครั้งเดียว API จะส่งคืนส่วนย่อยของข้อมูลที่มีขนาดเล็กและคาดการณ์ได้ สิ่งสำคัญคือ การตอบสนองของ API ยังรวมถึง metadata ที่ช่วยให้ client สามารถดึงข้อมูลส่วนย่อยถัดไปได้ทีละน้อยหากต้องการข้อมูลเพิ่มเติม
กระบวนการนี้เปรียบได้กับหน้าต่างๆ ของหนังสือ หรือผลการค้นหาบน Google คุณจะได้รับผลลัพธ์หน้าแรก พร้อมด้วยปุ่มควบคุมเพื่อนำทางไปยังหน้าสอง หน้าสาม และอื่นๆ ตามที่ชุมชนนักพัฒนาเช่น DEV Community และแพลตฟอร์มอย่าง Merge.dev ชี้ให้เห็น นี่คือกระบวนการแบ่งชุดข้อมูลขนาดใหญ่ออกเป็นส่วนย่อยๆ ซึ่ง client สามารถดึงข้อมูลได้ทีละน้อยหากต้องการข้อมูลทั้งหมดจริงๆ นี่คือแนวคิดพื้นฐานสำหรับการสร้างแอปพลิเคชันที่แข็งแกร่งและปรับขนาดได้
ทำไมการแบ่งหน้าจึงเป็นข้อกำหนดหลักในการออกแบบ API สมัยใหม่?
แรงจูงใจหลักสำหรับการแบ่งหน้าคือเพื่อให้แน่ใจว่าการตอบสนองของ API จัดการได้ง่ายขึ้นสำหรับทั้ง server และ client หากไม่มีการแบ่งหน้า แอปพลิเคชันจะเผชิญกับข้อจำกัดที่รุนแรงและประสบการณ์ผู้ใช้ที่แย่ ประโยชน์หลักได้แก่:
- ประสิทธิภาพที่ดีขึ้นและลดความหน่วง: ข้อได้เปรียบที่สำคัญที่สุดคือความเร็ว การถ่ายโอน JSON payload ขนาดเล็ก 25 รายการเร็วกว่าการถ่ายโอน payload 2.5 ล้านรายการหลายเท่า สิ่งนี้นำไปสู่ความรู้สึกที่รวดเร็วและตอบสนองสำหรับผู้ใช้ปลายทาง
- ความน่าเชื่อถือของ API ที่เพิ่มขึ้น: การตอบสนอง HTTP ขนาดใหญ่มีโอกาสสูงที่จะล้มเหลวระหว่างการถ่ายโอนเนื่องจาก network timeouts, dropped connections, หรือ client-side memory limits การแบ่งหน้าสร้างคำขอที่เล็กลงและยืดหยุ่นมากขึ้น หากหน้าใดหน้าหนึ่งโหลดไม่สำเร็จ client สามารถลองส่งคำขอเฉพาะหน้านั้นซ้ำได้โดยไม่ต้องเริ่มการถ่ายโอนข้อมูลทั้งหมดใหม่
- ลดภาระของ Server: การสร้างการตอบสนองขนาดใหญ่อาจทำให้ทรัพยากรของ server ตึงเครียดอย่างมาก การ query ฐานข้อมูลอาจช้า และการ serialize ข้อมูลหลายล้านรายการเป็น JSON ใช้ CPU และ memory จำนวนมาก การแบ่งหน้าช่วยให้ server สามารถ query ได้เล็กลงและมีประสิทธิภาพมากขึ้น ปรับปรุงความสามารถโดยรวมและความสามารถในการให้บริการ client หลายรายพร้อมกัน
- การประมวลผลฝั่ง Client ที่มีประสิทธิภาพ: สำหรับแอปพลิเคชัน client โดยเฉพาะอย่างยิ่งที่ทำงานบนอุปกรณ์มือถือหรือใน web browser การ parse JSON object ขนาดใหญ่อาจทำให้ user interface ค้างและนำไปสู่ประสบการณ์ที่น่าหงุดหงิด ข้อมูลส่วนย่อยที่เล็กกว่าจะ parse และ render ได้ง่ายกว่า ส่งผลให้แอปพลิเคชันทำงานได้ราบรื่นขึ้น
กลยุทธ์และเทคนิคการแบ่งหน้าทั่วไป
มีหลายวิธีในการนำการแบ่งหน้าไปใช้ แต่มีสองกลยุทธ์หลักที่กลายเป็นมาตรฐานโดยพฤตินัยในอุตสาหกรรม การเลือกระหว่างสองวิธีนี้มีผลกระทบอย่างมากต่อประสิทธิภาพ ความสอดคล้องของข้อมูล และประสบการณ์ผู้ใช้
Offset-Based Pagination: แนวทางพื้นฐาน
Offset-based pagination ซึ่งมักเรียกว่า "page-number pagination" มักเป็นแนวทางแรกที่นักพัฒนาเรียนรู้ เป็นแนวคิดที่เรียบง่ายและพบเห็นได้ในแอปพลิเคชัน web จำนวนมาก ทำงานโดยใช้สองพารามิเตอร์หลัก:
limit
(หรือpage_size
): จำนวนผลลัพธ์สูงสุดที่จะส่งคืนในหน้าเดียวoffset
(หรือpage
): จำนวนรายการที่จะข้ามจากจุดเริ่มต้นของชุดข้อมูล หากใช้พารามิเตอร์page
โดยทั่วไป offset จะคำนวณเป็น(page - 1) * limit
คำขอทั่วไปมีลักษณะดังนี้: GET /api/products?limit=25&offset=50
ซึ่งจะแปลเป็น SQL query ดังนี้:SQL
SELECT * FROM products ORDER BY created_at DESC LIMIT 25 OFFSET 50;
query นี้จะข้าม 50 รายการแรกและดึงข้อมูล 25 รายการถัดไป (เช่น รายการที่ 51-75)
ข้อดี:
- ความเรียบง่าย: วิธีนี้ตรงไปตรงมาในการนำไปใช้ ดังที่แสดงในบทช่วยสอนจำนวนมาก เช่น "Node.js REST API: Offset Pagination Made Easy"
- Stateless Navigation: Client สามารถข้ามไปยังหน้าใดก็ได้ในชุดข้อมูลได้อย่างง่ายดายโดยไม่ต้องมีข้อมูลก่อนหน้า ทำให้เหมาะสำหรับ UI ที่มีลิงก์หน้าเป็นตัวเลข
ข้อเสียและข้อจำกัด:
- ประสิทธิภาพต่ำสำหรับชุดข้อมูลขนาดใหญ่: ข้อเสียหลักคือ clause
OFFSET
ของฐานข้อมูล สำหรับคำขอที่มี offset ขนาดใหญ่ (เช่นOFFSET 1000000
) ฐานข้อมูลยังคงต้องดึงข้อมูลทั้งหมด 1,000,025 รายการจากดิสก์ นับผ่านล้านรายการแรกเพื่อข้าม และหลังจากนั้นจึงส่งคืน 25 รายการสุดท้ายเท่านั้น สิ่งนี้อาจช้าอย่างไม่น่าเชื่อเมื่อหมายเลขหน้าเพิ่มขึ้น - ความไม่สอดคล้องของข้อมูล (Page Drift): หากมีการเขียนรายการใหม่ลงในฐานข้อมูลขณะที่ผู้ใช้กำลังแบ่งหน้า ชุดข้อมูลทั้งหมดจะเลื่อน ผู้ใช้ที่นำทางจากหน้า 2 ไปยังหน้า 3 อาจเห็นรายการที่ซ้ำกันจากท้ายหน้า 2 หรือพลาดรายการไปโดยสิ้นเชิง นี่เป็นปัญหาสำคัญสำหรับแอปพลิเคชันแบบเรียลไทม์ และเป็นหัวข้อทั่วไปในฟอรัมนักพัฒนาเช่น Stack Overflow เมื่อพูดถึงวิธีตรวจสอบความสอดคล้องของข้อมูล
Cursor-Based (Keyset) Pagination: โซลูชันที่ปรับขนาดได้
Cursor-based pagination หรือที่เรียกว่า keyset หรือ seek pagination แก้ปัญหาประสิทธิภาพและความสอดคล้องของวิธี offset แทนที่จะใช้หมายเลขหน้า จะใช้ "cursor" ซึ่งเป็นตัวชี้ที่เสถียรและไม่โปร่งใสไปยังรายการเฉพาะในชุดข้อมูล
ขั้นตอนมีดังนี้:
- Client ทำการร้องขอหน้าข้อมูลครั้งแรก
- Server ส่งคืนหน้าข้อมูล พร้อมด้วย cursor ที่ชี้ไปยังรายการสุดท้ายในชุดนั้น
- สำหรับหน้าถัดไป client จะส่ง cursor นั้นกลับไปยัง server
- Server จะดึงข้อมูลรายการที่อยู่ หลังจาก cursor นั้นอย่างมีประสิทธิภาพ "seek" ไปยังจุดนั้นในชุดข้อมูล
โดยทั่วไป cursor จะเป็นค่าที่เข้ารหัสซึ่งได้มาจากคอลัมน์ที่กำลังเรียงลำดับ ตัวอย่างเช่น หากเรียงลำดับตาม created_at
(timestamp) cursor อาจเป็น timestamp ของรายการสุดท้าย เพื่อจัดการกับรายการที่มีค่าเท่ากัน มักจะรวมคอลัมน์ที่สองที่ไม่ซ้ำกัน (เช่น id
ของรายการ) เข้าไปด้วย
คำขอที่ใช้ cursor มีลักษณะดังนี้: GET /api/products?limit=25&after_cursor=eyJjcmVhdGVkX2F0IjoiMjAyNS0wNi0wN1QxODowMDowMC4wMDBaIiwiaWQiOjg0N30=
ซึ่งจะแปลเป็น SQL query ที่มีประสิทธิภาพมากกว่ามาก:SQL
SELECT * FROM products
WHERE (created_at, id) < ('2025-06-07T18:00:00.000Z', 847)
ORDER BY created_at DESC, id DESC
LIMIT 25;
query นี้ใช้ index บน (created_at, id)
เพื่อ "seek" ไปยังจุดเริ่มต้นที่ถูกต้องทันที หลีกเลี่ยงการ scan ตารางทั้งหมด และทำให้รวดเร็วอย่างสม่ำเสมอโดยไม่คำนึงถึงความลึกที่ผู้ใช้กำลังแบ่งหน้า
ข้อดี:
- ประสิทธิภาพสูงและปรับขนาดได้: ประสิทธิภาพของฐานข้อมูลรวดเร็วและคงที่ ทำให้เหมาะสำหรับชุดข้อมูลทุกขนาด
- ความสอดคล้องของข้อมูล: เนื่องจาก cursor เชื่อมโยงกับรายการเฉพาะ ไม่ใช่ตำแหน่งสัมบูรณ์ ข้อมูลใหม่ที่เพิ่มหรือลบจะไม่ทำให้รายการหายไปหรือซ้ำกันระหว่างหน้า
ข้อเสีย:
- ความซับซ้อนในการนำไปใช้: Logic สำหรับการสร้างและ parse cursors ซับซ้อนกว่าการคำนวณ offset แบบง่าย
- การนำทางที่จำกัด: Client สามารถนำทางไปยังหน้า "ถัดไป" หรือ "ก่อนหน้า" เท่านั้น ไม่สามารถข้ามไปยังหมายเลขหน้าเฉพาะได้โดยตรง ทำให้ไม่เหมาะสำหรับรูปแบบ UI บางอย่าง
- ต้องใช้ Stable Sort Key: การนำไปใช้เชื่อมโยงอย่างแน่นหนาตามลำดับการเรียงลำดับและต้องมีคอลัมน์ที่ไม่ซ้ำกันและเรียงลำดับอย่างน้อยหนึ่งคอลัมน์
การเปรียบเทียบการแบ่งหน้าสองประเภทหลัก
การเลือกระหว่าง offset และ cursor pagination ขึ้นอยู่กับ use case โดยสิ้นเชิง
คุณสมบัติ | การแบ่งหน้าแบบ Offset | การแบ่งหน้าแบบ Cursor |
ประสิทธิภาพ | ต่ำสำหรับหน้าที่มี offset ลึกในชุดข้อมูลขนาดใหญ่ | ยอดเยี่ยมและสม่ำเสมอในทุกความลึก |
ความสอดคล้องของข้อมูล | มีแนวโน้มที่จะพลาด/ซ้ำข้อมูล (page drift) | สูง; ข้อมูลใหม่ไม่ส่งผลต่อการแบ่งหน้า |
การนำทาง | สามารถข้ามไปยังหน้าใดก็ได้ | จำกัดเฉพาะหน้าถัดไป/ก่อนหน้า |
การนำไปใช้ | เรียบง่ายและตรงไปตรงมา | ซับซ้อนกว่า; ต้องใช้ logic ของ cursor |
กรณีการใช้งานที่เหมาะสม | ชุดข้อมูลขนาดเล็กและคงที่; admin UI | ฟีดแบบ infinite scroll; ชุดข้อมูลขนาดใหญ่และเปลี่ยนแปลงตลอดเวลา |
แนวทางปฏิบัติที่ดีที่สุดสำหรับการนำ Server-Side Pagination ไปใช้
ไม่ว่ากลยุทธ์ที่เลือกจะเป็นแบบใด การปฏิบัติตามชุดแนวทางปฏิบัติที่ดีที่สุดจะส่งผลให้ได้ API ที่สะอาด คาดการณ์ได้ และใช้งานง่าย นี่มักเป็นส่วนสำคัญในการตอบคำถาม "แนวทางปฏิบัติที่ดีที่สุดของ server-side pagination คืออะไร?"
การออกแบบ Pagination Response Payload
ข้อผิดพลาดทั่วไปคือการส่งคืนเฉพาะ array ของผลลัพธ์ pagination response payload ที่ออกแบบมาอย่างดีควรเป็น object ที่ "ห่อหุ้ม" ข้อมูลและรวม metadata การแบ่งหน้าที่ชัดเจนJSON
{
"data": [
{ "id": 101, "name": "Product A" },
{ "id": 102, "name": "Product B" }
],
"pagination": {
"next_cursor": "eJjcmVhdGVkX2F0Ij...",
"has_next_page": true
}
}
สำหรับการแบ่งหน้าแบบ offset metadata จะมีลักษณะแตกต่างกัน:JSON
{
"data": [
// ... results
],
"metadata": {
"total_results": 8452,
"total_pages": 339,
"current_page": 3,
"per_page": 25
}
}
โครงสร้างนี้ทำให้ client ทราบได้อย่างง่ายดายว่ามีข้อมูลเพิ่มเติมให้ดึงหรือไม่ หรือจะ render ปุ่มควบคุม UI อย่างไร
การใช้ Hypermedia Links สำหรับการนำทาง (HATEOAS)
หลักการสำคัญของ REST คือ HATEOAS (Hypermedia as the Engine of Application State) ซึ่งหมายความว่า API ควรมีลิงก์ให้ client นำทางไปยัง resource หรือ action อื่นๆ สำหรับการแบ่งหน้า นี่เป็นสิ่งที่มีประสิทธิภาพอย่างไม่น่าเชื่อ ดังที่แสดงใน GitHub Docs วิธีมาตรฐานในการทำเช่นนี้คือการใช้ HTTP header Link
Link: <https://api.example.com/items?page=3>; rel="next", <https://api.example.com/items?page=1>; rel="prev"
อีกทางเลือกหนึ่ง ลิงก์เหล่านี้สามารถวางไว้ใน JSON response body ได้โดยตรง ซึ่งมักจะง่ายกว่าสำหรับ JavaScript client ในการใช้งาน:JSON
"pagination": {
"links": {
"next": "https://api.example.com/items?limit=25&offset=75",
"previous": "https://api.example.com/items?limit=25&offset=25"
}
}
สิ่งนี้ช่วยให้ client ไม่ต้องสร้าง URL ด้วยตนเอง
อนุญาตให้ Client ควบคุมขนาดหน้า
เป็นแนวทางปฏิบัติที่ดีที่จะอนุญาตให้ client ร้องขอหน้าผลลัพธ์เพิ่มเติมสำหรับการตอบสนองแบบแบ่งหน้า และ เปลี่ยนจำนวนผลลัพธ์ที่ส่งคืนในแต่ละหน้า โดยทั่วไปจะทำได้โดยใช้ query parameter limit
หรือ per_page
อย่างไรก็ตาม server ควรบังคับใช้ limit สูงสุดที่เหมาะสมเสมอ (เช่น 100) เพื่อป้องกันไม่ให้ client ร้องขอข้อมูลมากเกินไปในครั้งเดียวและทำให้ระบบทำงานหนักเกินไป
การรวมการแบ่งหน้ากับการกรองและการเรียงลำดับ
API ในโลกแห่งความเป็นจริงไม่ค่อยแบ่งหน้าอย่างเดียว มักจะต้องรองรับการกรองและการเรียงลำดับด้วย ดังที่แสดงในบทช่วยสอนที่ครอบคลุมเทคโนโลยีต่างๆ เช่น .NET การเพิ่มคุณสมบัติเหล่านี้เป็นข้อกำหนดทั่วไป
คำขอที่ซับซ้อนอาจมีลักษณะดังนี้: GET /api/products?status=published&sort=-created_at&limit=50&page=2
เมื่อนำสิ่งนี้ไปใช้ สิ่งสำคัญคือต้องพิจารณาพารามิเตอร์การกรองและการเรียงลำดับเป็นส่วนหนึ่งของ logic การแบ่งหน้า ลำดับการ sort
ต้องมีความเสถียรและกำหนดได้เพื่อให้การแบ่งหน้าทำงานได้อย่างถูกต้อง หากลำดับการเรียงลำดับไม่ซ้ำกัน คุณต้องเพิ่มคอลัมน์ที่สองที่ไม่ซ้ำกัน (เช่น id
) เพื่อทำหน้าที่เป็น tie-breaker เพื่อให้แน่ใจว่าลำดับมีความสอดคล้องกันระหว่างหน้า
ตัวอย่างการนำไปใช้จริง
มาสำรวจวิธีการนำแนวคิดเหล่านี้ไปใช้ใน framework ยอดนิยมต่างๆ
การแบ่งหน้า REST API ใน Python ด้วย Django REST Framework
หนึ่งในการรวมกันที่ได้รับความนิยมมากที่สุดสำหรับการสร้าง API คือ Python กับ Django REST Framework (DRF) DRF ให้การสนับสนุนการแบ่งหน้าที่ทรงพลังและสร้างขึ้นในตัว ทำให้เริ่มต้นได้ง่ายอย่างไม่น่าเชื่อ มี class สำหรับกลยุทธ์ต่างๆ:
PageNumberPagination
: สำหรับการแบ่งหน้าแบบ offset ตามหมายเลขหน้ามาตรฐานLimitOffsetPagination
: สำหรับการนำ offset ไปใช้ที่ยืดหยุ่นมากขึ้นCursorPagination
: สำหรับการแบ่งหน้าแบบ cursor ที่มีประสิทธิภาพสูง
คุณสามารถกำหนดรูปแบบการแบ่งหน้าเริ่มต้นทั่วโลก จากนั้นเพียงแค่ใช้ ListAPIView
ทั่วไป และ DRF จะจัดการส่วนที่เหลือทั้งหมด นี่คือตัวอย่าง Rest api pagination python ที่ยอดเยี่ยมPython
# In your settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
'PAGE_SIZE': 50
}
# In your views.py
class ProductListView(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
# DRF handles the entire pagination logic automatically!
การสร้าง Paginated REST API ด้วย Node.js, Express และ TypeScript
ในระบบนิเวศ Node.js คุณมักจะต้องสร้าง logic การแบ่งหน้าด้วยตนเอง ซึ่งให้คุณควบคุมได้อย่างเต็มที่ ส่วนของคู่มือนี้จะให้ภาพรวมแนวคิดของการสร้างการแบ่งหน้าด้วย Node.js, Express และ TypeScript
นี่คือตัวอย่างที่เรียบง่ายของการนำ cursor pagination ไปใช้:TypeScript
// In your Express controller
app.get('/products', async (req: Request, res: Response) => {
const limit = parseInt(req.query.limit as string) || 25;
const cursor = req.query.cursor as string;
let query = db.selectFrom('products').orderBy('createdAt', 'desc').orderBy('id', 'desc').limit(limit);
if (cursor) {
const { createdAt, id } = JSON.parse(Buffer.from(cursor, 'base64').toString('ascii'));
// Add the WHERE clause for the cursor
query = query.where('createdAt', '<=', createdAt).where('id', '<', id);
}
const products = await query.execute();
const nextCursor = products.length > 0
? Buffer.from(JSON.stringify({
createdAt: products[products.length - 1].createdAt,
id: products[products.length - 1].id
})).toString('base64')
: null;
res.json({
data: products,
pagination: { next_cursor: nextCursor }
});
});
การแบ่งหน้าในระบบนิเวศ Java หรือ .NET
Frameworks ในระบบนิเวศอื่น ๆ ก็มีการสนับสนุนการแบ่งหน้าที่แข็งแกร่งเช่นกัน
- Java (Spring Boot): โปรเจกต์ Spring Data ทำให้การแบ่งหน้าเป็นเรื่องง่าย การใช้
PagingAndSortingRepository
คุณสามารถกำหนด signature ของ method เช่นPage<Product> findAll(Pageable pageable);
Spring จะนำ method ไปใช้โดยอัตโนมัติ จัดการพารามิเตอร์คำขอpage
,size
และsort
และส่งคืน objectPage
ที่มีผลลัพธ์และ metadata การแบ่งหน้าที่จำเป็นทั้งหมด นี่คือคำตอบที่เป็นแนวทางปฏิบัติที่ดีที่สุดสำหรับ "วิธีการนำการแบ่งหน้าไปใช้ใน Java REST API?" - .NET: ในโลก .NET นักพัฒนามักใช้
IQueryable
extensions ด้วย method เช่น.Skip()
และ.Take()
เพื่อนำ offset pagination ไปใช้ สำหรับสถานการณ์ที่ซับซ้อนมากขึ้น library ต่างๆ สามารถช่วยสร้างโซลูชันแบบ cursor-based ที่แปลเป็น SQL query ที่มีประสิทธิภาพได้
### กรณีการใช้งานจริง: การแบ่งหน้า Product Catalog API
พิจารณาเว็บไซต์ e-commerce ที่มี "Product Catalog API" นี่คือ กรณีการใช้งานจริง ที่สมบูรณ์แบบ แค็ตตาล็อกมีขนาดใหญ่และเปลี่ยนแปลงตลอดเวลา โดยมีการเพิ่มผลิตภัณฑ์ใหม่บ่อยครั้ง
- ปัญหา: หากเว็บไซต์ใช้ offset pagination สำหรับรายการผลิตภัณฑ์ และมีการเพิ่มผลิตภัณฑ์ใหม่ในขณะที่ลูกค้ากำลังเรียกดูจากหน้า 1 ไปยังหน้า 2 ลูกค้าอาจเห็นผลิตภัณฑ์สุดท้ายจากหน้า 1 ซ้ำที่ด้านบนของหน้า 2 นี่เป็นประสบการณ์ผู้ใช้ที่สับสน
- โซลูชัน: การนำ cursor-based pagination ไปใช้คือวิธีแก้ปัญหาที่เหมาะสม ปุ่ม "โหลดเพิ่มเติม" ที่ส่วนหน้าจะส่ง cursor ของผลิตภัณฑ์ที่มองเห็นล่าสุด API จะส่งคืนชุดผลิตภัณฑ์ถัดไปหลังจากรายการเฉพาะนั้น เพื่อให้แน่ใจว่ารายการจะเพิ่มขึ้นโดยไม่มีรายการซ้ำหรือรายการที่หายไปสำหรับผู้ใช้
หัวข้อขั้นสูงและปัญหาทั่วไป
ดังที่นักพัฒนาบน Stack Overflow และ Reddit มักพบ การสร้างระบบการแบ่งหน้าที่แข็งแกร่งอย่างแท้จริงต้องจัดการกับรายละเอียดและ edge cases จำนวนมาก
วิธีตรวจสอบความสอดคล้องของข้อมูลใน Paginated API
นี่เป็นหนึ่งในหัวข้อขั้นสูงที่สำคัญที่สุด ดังที่กล่าวไปแล้ว วิธีเดียวที่เชื่อถือได้ในการรับประกันความสอดคล้องของข้อมูลในระบบที่มีการเขียนข้อมูลบ่อยครั้งคือการใช้ keyset/cursor pagination การออกแบบของมันป้องกัน page drift ได้โดยธรรมชาติ หากด้วยเหตุผลบางอย่างคุณติดอยู่กับการใช้ offset pagination มีวิธีแก้ปัญหาที่ซับซ้อนบางอย่าง เช่น การสร้าง snapshot ของ ID ที่ไม่เปลี่ยนแปลงชั่วคราวสำหรับชุดผลลัพธ์ทั้งหมดและแบ่งหน้าผ่านรายการนั้น แต่วิธีนี้มีสถานะสูงและโดยทั่วไปไม่แนะนำสำหรับ REST API
การจัดการ Edge Cases ที่แปลกประหลาด
API ที่พร้อมใช้งานจริงต้องจัดการกับ input ที่ไม่ถูกต้องได้อย่างราบรื่น พิจารณา edge cases ทั่วไปเหล่านี้:
- Client ร้องขอ
page=0
หรือoffset=-50
API ไม่ควร throw error 500 ควรส่งคืน400 Bad Request
พร้อมข้อความแสดงข้อผิดพลาดที่ชัดเจน - Client ให้
cursor
ที่ผิดรูปแบบหรือไม่ถูกต้อง API ควรส่งคืน400 Bad Request
อีกครั้ง - Client ให้ cursor ที่ถูกต้อง แต่รายการที่ชี้ไปถูกลบไปแล้ว กลยุทธ์ที่ดีคือการถือว่า cursor ชี้ไปยัง "พื้นที่" ที่รายการนั้นเคยอยู่ และส่งคืนหน้าผลลัพธ์ถัดไปจากจุดนั้น
การนำไปใช้ฝั่ง Client
ฝั่ง client คือที่ที่ logic การแบ่งหน้าถูกนำไปใช้ การใช้ JavaScript เพื่อ ดึงข้อมูลแบบแบ่งหน้าจาก REST API อย่างมืออาชีพ เกี่ยวข้องกับการอ่าน metadata การแบ่งหน้าและใช้เพื่อส่งคำขอถัดไป
นี่คือตัวอย่าง fetch
แบบง่ายสำหรับปุ่ม "โหลดเพิ่มเติม" ที่ใช้ cursor pagination:JavaScript
const loadMoreButton = document.getElementById('load-more');
let nextCursor = null; // Store the cursor globally or in component state
async function fetchProducts(cursor) {
const url = cursor ? `/api/products?cursor=${cursor}` : '/api/products';
const response = await fetch(url);
const data = await response.json();
// ... render the new products ...
nextCursor = data.pagination.next_cursor;
if (!nextCursor) {
loadMoreButton.disabled = true; // No more pages
}
}
loadMoreButton.addEventListener('click', () => fetchProducts(nextCursor));
// Initial load
fetchProducts(null);
อนาคตของการดึงข้อมูล API และมาตรฐานการแบ่งหน้า
แม้ว่า REST จะครองตลาดมาหลายปี แต่ภูมิทัศน์ก็มีการพัฒนาอยู่เสมอ
มาตรฐานการแบ่งหน้า REST API ที่กำลังพัฒนา
ไม่มี RFC เดียวที่เป็นทางการที่กำหนด มาตรฐานการแบ่งหน้า REST API อย่างไรก็ตาม มีชุดของข้อตกลงที่แข็งแกร่งเกิดขึ้น ซึ่งขับเคลื่อนโดย public API ของบริษัทเทคโนโลยีรายใหญ่ เช่น GitHub, Stripe และ Atlassian ข้อตกลงเหล่านี้ เช่น การใช้ header Link
และการให้ metadata ที่ชัดเจน ได้กลายเป็นมาตรฐานโดยพฤตินัย ความสอดคล้องเป็นสิ่งสำคัญ แพลตฟอร์ม API ที่ออกแบบมาอย่างดีจะใช้กลยุทธ์การแบ่งหน้าเดียวกันในทุก endpoint ที่เป็นรายการ
ผลกระทบของ GraphQL ต่อการแบ่งหน้า
GraphQL นำเสนอโมเดลที่แตกต่างกัน แทนที่จะมีหลาย endpoint มี endpoint เดียวที่ client ส่ง query ที่ซับซ้อนซึ่งระบุข้อมูลที่ต้องการ อย่างไรก็ตาม ความจำเป็นในการแบ่งรายการข้อมูลขนาดใหญ่ไม่ได้หายไป ชุมชน GraphQL ยังได้กำหนดมาตรฐานการแบ่งหน้าแบบ cursor ผ่านข้อกำหนดที่เป็นทางการที่เรียกว่า Relay Cursor Connections Spec ซึ่งกำหนดโครงสร้างที่แม่นยำสำหรับการแบ่งหน้าข้อมูล โดยใช้แนวคิดเช่น first
, after
, last
และ before
เพื่อให้การแบ่งหน้าไปข้างหน้าและย้อนกลับที่แข็งแกร่ง
สรุป: สรุปแนวทางปฏิบัติที่ดีที่สุดสำหรับการแบ่งหน้า
การเชี่ยวชาญ การแบ่งหน้า REST API เป็นทักษะที่สำคัญสำหรับนักพัฒนา backend ทุกคน เป็นเทคนิคที่จำเป็นสำหรับการสร้างแอปพลิเคชันที่ปรับขนาดได้ มีประสิทธิภาพ และใช้งานง่าย
เพื่อสรุป แนวทางปฏิบัติที่ดีที่สุดสำหรับการแบ่งหน้า REST API:
- แบ่งหน้าเสมอ: อย่าส่งคืนรายการผลลัพธ์ที่ไม่จำกัดจาก API endpoint
- เลือกกลยุทธ์ที่เหมาะสม: ใช้ offset pagination แบบง่ายสำหรับชุดข้อมูลขนาดเล็ก ไม่สำคัญ หรือคงที่ สำหรับชุดข้อมูลขนาดใหญ่ เปลี่ยนแปลงตลอดเวลา หรือที่ผู้ใช้ต้องเผชิญ ควรเลือก cursor-based pagination อย่างยิ่งเนื่องจากมีประสิทธิภาพและความสอดคล้องของข้อมูลที่เหนือกว่า
- ให้ Metadata ที่ชัดเจน: Response payload ของคุณควรรวมข้อมูลที่บอก client ถึงวิธีการรับข้อมูลหน้าถัดไปเสมอ ไม่ว่าจะเป็น
next_cursor
หรือหมายเลขหน้าและลิงก์ - ใช้ Hypermedia: ใช้ header
Link
หรือลิงก์ภายใน JSON body ของคุณเพื่อให้ API ของคุณค้นพบและใช้งานง่ายขึ้น - จัดการข้อผิดพลาดอย่างสง่างาม: ตรวจสอบพารามิเตอร์การแบ่งหน้าทั้งหมดและส่งคืนข้อผิดพลาด
400 Bad Request
ที่ชัดเจนสำหรับ input ที่ไม่ถูกต้อง
ด้วยการปฏิบัติตามคู่มือนี้และซึมซับหลักการเหล่านี้ คุณสามารถออกแบบและสร้าง REST API ระดับมืออาชีพที่พร้อมใช้งานจริง ซึ่งสามารถปรับขนาดได้อย่างมีประสิทธิภาพเพื่อตอบสนองความต้องการใดๆ
คำถามที่พบบ่อยเกี่ยวกับการแบ่งหน้า REST API
1. ความแตกต่างหลักระหว่าง offset และ cursor pagination คืออะไร?
ความแตกต่างหลักอยู่ที่วิธีการกำหนดชุดข้อมูลที่จะดึง Offset pagination ใช้ตัวเลข offset (เช่น "ข้าม 50 รายการแรก") เพื่อค้นหาหน้าถัดไป ซึ่งอาจช้าสำหรับชุดข้อมูลขนาดใหญ่เนื่องจากฐานข้อมูลยังคงต้องนับผ่านรายการที่ถูกข้าม Cursor pagination ใช้ตัวชี้หรือ "cursor" ที่เสถียรซึ่งชี้ไปยังรายการเฉพาะ (เช่น "รับรายการหลัง product ID 857") วิธีนี้มีประสิทธิภาพมากกว่ามากเนื่องจากฐานข้อมูลสามารถใช้ index เพื่อข้ามไปยังรายการนั้นได้โดยตรง
2. เมื่อใดที่เหมาะสมที่จะใช้ offset pagination แทน cursor pagination?
Offset pagination เหมาะสำหรับชุดข้อมูลที่มีขนาดเล็ก ไม่สำคัญต่อประสิทธิภาพ หรือไม่ค่อยเปลี่ยนแปลง ข้อได้เปรียบหลักคือความเรียบง่ายและความสามารถสำหรับผู้ใช้ในการข้ามไปยังหมายเลขหน้าเฉพาะ (เช่น "ไปที่หน้า 10") ทำให้เหมาะสำหรับสิ่งต่างๆ เช่น admin dashboards หรือเครื่องมือภายในที่ประสบการณ์ผู้ใช้ในการข้ามระหว่างหน้าสำคัญกว่าการจัดการการเปลี่ยนแปลงข้อมูลแบบเรียลไทม์
3. Cursor-based pagination ป้องกันปัญหาการข้ามหรือซ้ำรายการได้อย่างไร?
Cursor-based pagination ป้องกันความไม่สอดคล้องของข้อมูลเนื่องจากยึดคำขอถัดไปกับรายการเฉพาะ ไม่ใช่ตำแหน่งตัวเลข ตัวอย่างเช่น หากคุณร้องขอหน้า หลังจาก รายการที่มี ID=100
ไม่สำคัญว่ามีการเพิ่มรายการใหม่ก่อนหน้านั้นหรือไม่ query จะเริ่มดึงข้อมูลจากตำแหน่งที่ถูกต้องเสมอ ด้วย offset pagination หากมีการเพิ่มรายการใหม่ในหน้า 1 ในขณะที่คุณกำลังดู เมื่อคุณร้องขอหน้า 2 รายการสุดท้ายจากหน้า 1 จะกลายเป็นรายการแรกในหน้า 2 ทำให้เกิดการซ้ำซ้อน
4. มีมาตรฐานที่เป็นทางการสำหรับการตอบสนองการแบ่งหน้า REST API หรือไม่?
ไม่มี RFC เดียวที่เป็นทางการหรือมาตรฐานที่เป็นทางการที่กำหนดว่าการแบ่งหน้า REST API ทั้งหมดจะต้องนำไปใช้อย่างไร อย่างไรก็ตาม มีข้อตกลงและแนวทางปฏิบัติที่ดีที่สุดที่แข็งแกร่งเกิดขึ้นจากอุตสาหกรรม ส่วนใหญ่กำหนดโดย public API หลักๆ เช่น ของ GitHub และ Stripe ข้อตกลงเหล่านี้รวมถึงการใช้ HTTP header Link
ที่มี attribute rel="next"
และ rel="prev"
หรือการฝัง object pagination
ที่มี metadata และลิงก์ที่ชัดเจนโดยตรงใน JSON response body
5. ฉันควรจัดการการเรียงลำดับและการกรองกับ paginated endpoints ของฉันอย่างไร?
การเรียงลำดับและการกรองควรถูกนำไปใช้ ก่อน การแบ่งหน้า ผลลัพธ์ที่แบ่งหน้าควรเป็น "มุมมอง" ของชุดข้อมูลที่เรียงลำดับและกรองแล้ว สิ่งสำคัญคือลำดับการเรียงลำดับต้องมีความเสถียรและกำหนดได้ หากผู้ใช้เรียงลำดับตาม field ที่ไม่ซ้ำกัน (เช่น วันที่) คุณต้องเพิ่ม secondary sort key ที่ไม่ซ้ำกัน (เช่น id
ของรายการ) เพื่อทำหน้าที่เป็น tie-breaker สิ่งนี้ทำให้แน่ใจว่าลำดับของรายการเหมือนกันเสมอ ซึ่งจำเป็นสำหรับการทำงานที่ถูกต้องของทั้ง offset และ cursor pagination
ต้องการแพลตฟอร์มแบบครบวงจรที่ช่วยให้ทีมพัฒนาของคุณทำงานร่วมกันได้อย่าง มีประสิทธิภาพสูงสุด?
Apidog ตอบสนองทุกความต้องการของคุณ และ แทนที่ Postman ในราคาที่เข้าถึงได้มากกว่ามาก!