การแบ่งหน้า API ที่ดีที่สุด


288

ฉันชอบความช่วยเหลือในการจัดการเคสขอบแปลก ๆ ด้วย API ที่ฉันสร้างขึ้น

เช่นเดียวกับ API จำนวนมากอันนี้แบ่งหน้าผลลัพธ์ขนาดใหญ่ หากคุณสอบถาม / foos คุณจะได้รับ 100 ผลลัพธ์ (เช่น foo # 1-100) และลิงก์ไปยัง / foos? page = 2 ซึ่งควรส่งคืน foo # 101-200

น่าเสียดายที่หาก foo # 10 ถูกลบออกจากชุดข้อมูลก่อนที่ผู้บริโภค API จะทำการสืบค้นต่อไป / foos? page = 2 จะชดเชย 100 และส่งกลับ foos # 102-201

ปัญหานี้เป็นปัญหาสำหรับผู้ใช้ API ที่พยายามดึง foos ทั้งหมด - พวกเขาจะไม่ได้รับ foo # 101

วิธีที่ดีที่สุดในการจัดการกับสิ่งนี้คืออะไร? เราต้องการทำให้มีน้ำหนักเบาที่สุดเท่าที่จะเป็นไปได้ (เช่นหลีกเลี่ยงการจัดการเซสชันสำหรับคำขอ API) ตัวอย่างจาก API อื่น ๆ จะได้รับการชื่นชมอย่างมาก!


1
ปัญหานี่คืออะไร ดูเหมือนว่าฉันจะโอเคผู้ใช้จะได้รับ 100 รายการ
NARKOZ

2
ฉันกำลังเผชิญปัญหาเดียวกันนี้และกำลังมองหาวิธีแก้ปัญหา AFAIK ไม่มีกลไกที่รับประกันได้จริง ๆ ที่จะทำสิ่งนี้หากแต่ละหน้าเรียกใช้แบบสอบถามใหม่ ทางออกเดียวที่ฉันนึกถึงคือเก็บเซสชันที่ใช้งานอยู่และเก็บชุดผลลัพธ์ในฝั่งเซิร์ฟเวอร์และแทนที่จะเรียกใช้คิวรีใหม่สำหรับแต่ละหน้าเพียงหยิบชุดระเบียนถัดไปที่แคชไว้
Jerry Dodge

31
ดูว่า twitter บรรลุสิ่งนี้อย่างไร dev.twitter.com/rest/public/timelines
java_geek

1
@java_geek พารามิเตอร์ since_id ได้รับการปรับปรุงอย่างไร ในหน้าเว็บทวิตเตอร์ดูเหมือนว่าพวกเขากำลังร้องขอทั้งสองด้วยค่าเดียวกันสำหรับ since_id ฉันสงสัยว่าจะได้รับการอัปเดตเมื่อใดหากมีการเพิ่มทวีตใหม่ ๆ
Petar

1
@Petar พารามิเตอร์ since_id จำเป็นต้องได้รับการอัปเดตโดย consumer ของ API หากคุณเห็นตัวอย่างมีหมายถึงลูกค้าที่ประมวลผลทวีต
java_geek

คำตอบ:


176

ฉันไม่แน่ใจว่าวิธีการจัดการข้อมูลของคุณอย่างสมบูรณ์ดังนั้นสิ่งนี้อาจหรือไม่อาจใช้งานได้ แต่คุณคิดว่าการให้เลขหน้าด้วยฟิลด์ประทับหรือไม่

เมื่อคุณสอบถาม / foos คุณจะได้รับ 100 ผลลัพธ์ API ของคุณควรส่งคืนสิ่งนี้ (สมมติว่า JSON แต่ถ้าต้องการ XML ก็สามารถปฏิบัติตามหลักการเดียวกันได้):

{
    "data" : [
        {  data item 1 with all relevant fields    },
        {  data item 2   },
        ...
        {  data item 100 }
    ],
    "paging":  {
        "previous":  "http://api.example.com/foo?since=TIMESTAMP1" 
        "next":  "http://api.example.com/foo?since=TIMESTAMP2"
    }

}

เพียงแค่ทราบว่าการใช้การประทับเวลาเพียงครั้งเดียวนั้นขึ้นอยู่กับ 'ขีด จำกัด ' โดยนัยในผลลัพธ์ของคุณ คุณอาจต้องการเพิ่มข้อ จำกัด ที่ชัดเจนหรือใช้untilคุณสมบัติ

การประทับเวลาสามารถกำหนดแบบไดนามิกโดยใช้รายการข้อมูลสุดท้ายในรายการ นี่น่าจะเป็นสิ่งที่ Facebook ให้ความสำคัญมากขึ้นหรือน้อยลงในกราฟ API (เลื่อนลงไปด้านล่างเพื่อดูลิงก์การแบ่งหน้าในรูปแบบที่ฉันให้ไว้ด้านบน)

ปัญหาหนึ่งอาจเกิดขึ้นได้หากคุณเพิ่มรายการข้อมูล แต่ตามคำอธิบายของคุณดูเหมือนว่าพวกเขาจะถูกเพิ่มไปยังจุดสิ้นสุด (ถ้าไม่ใช่โปรดแจ้งให้เราทราบและฉันจะดูว่าฉันสามารถปรับปรุงได้หรือไม่)


30
การประทับเวลาจะไม่รับประกันว่าจะไม่ซ้ำกัน นั่นคือสามารถสร้างทรัพยากรหลายรายการด้วยการประทับเวลาเดียวกัน ดังนั้นวิธีการนี้มีข้อเสียที่หน้าถัดไปอาจทำซ้ำรายการสุดท้าย (ไม่กี่รายการ) จากหน้าปัจจุบัน
รูเบิล

4
@prmatta จริงขึ้นอยู่กับการใช้ฐานข้อมูลประทับเวลารับประกันได้ว่าจะไม่ซ้ำกัน
ramblinjan

2
@jandjorgensen จากลิงก์ของคุณ: "ประเภทข้อมูลการประทับเวลาเป็นเพียงตัวเลขที่เพิ่มขึ้นและไม่เก็บวันที่หรือเวลา ... ใน SQL Server 2008 และใหม่กว่าประเภทการประทับเวลาถูกเปลี่ยนชื่อเป็น rowversionน่าจะสะท้อนได้ดีกว่า วัตถุประสงค์และคุณค่า " ดังนั้นจึงไม่มีหลักฐานว่าการประทับเวลา (ผู้ที่มีค่าเวลา) ไม่ซ้ำกัน
โนแลนเอมี

3
@jandjorgensen ฉันชอบข้อเสนอของคุณ แต่คุณไม่ต้องการข้อมูลบางอย่างในลิงค์ทรัพยากรดังนั้นเรารู้ว่าถ้าเราไปก่อนหน้าหรือถัดไป Sth ต้องการ: "ก่อนหน้า": " api.example.com/foo?before=TIMESTAMP " "ถัดไป": " api.example.com/foo?since=TIMESTAMP2 " เราจะใช้รหัสลำดับของเราแทนการประทับเวลา คุณเห็นปัญหาใด ๆ หรือไม่?
longliveenduro

5
อีกตัวเลือกที่คล้ายกันคือใช้ฟิลด์ส่วนหัวลิงก์ที่ระบุใน RFC 5988 (ส่วนที่ 5): tools.ietf.org/html/rfc5988#page-6
Anthony F

28

คุณมีปัญหาหลายอย่าง

ก่อนอื่นคุณมีตัวอย่างที่คุณอ้างถึง

คุณยังมีปัญหาที่คล้ายกันหากมีการแทรกแถว แต่ในกรณีนี้ผู้ใช้จะได้รับข้อมูลที่ซ้ำกัน (เนื้อหาที่จัดการได้ง่ายกว่าข้อมูลที่หายไป แต่ยังคงเป็นปัญหา)

หากคุณไม่ได้จับภาพชุดข้อมูลดั้งเดิมอยู่นี่เป็นความจริงของชีวิต

คุณสามารถให้ผู้ใช้ทำภาพรวมที่ชัดเจน:

POST /createquery
filter.firstName=Bob&filter.lastName=Eubanks

ผลลัพธ์ใด:

HTTP/1.1 301 Here's your query
Location: http://www.example.org/query/12345

จากนั้นคุณสามารถเพจตลอดทั้งวันเนื่องจากตอนนี้เป็นแบบคงที่ สิ่งนี้อาจมีน้ำหนักเบาพอสมควรเนื่องจากคุณสามารถจับคีย์เอกสารที่แท้จริงแทนที่จะเป็นทั้งแถว

หากกรณีการใช้งานเป็นเพียงการที่ผู้ใช้ของคุณต้องการ (และต้องการ) ข้อมูลทั้งหมดคุณสามารถมอบให้กับพวกเขาได้:

GET /query/12345?all=true

และเพียงส่งชุดทั้งหมด


1
(การเรียงลำดับเริ่มต้นของ foos คือวันที่สร้างดังนั้นการแทรกแถวไม่ใช่ปัญหา)
2arrs2ells

ที่จริงแล้วการจับคีย์เอกสารเท่านั้นไม่เพียงพอ วิธีนี้คุณจะต้องค้นหาวัตถุทั้งหมดด้วย ID เมื่อผู้ใช้ร้องขอ แต่อาจเป็นไปได้ว่าพวกเขาไม่มีอยู่อีกต่อไป
Scadge

27

หากคุณมีเลขหน้าคุณเรียงลำดับข้อมูลด้วยคีย์ ทำไมไม่ให้ลูกค้า API รวมคีย์ขององค์ประกอบสุดท้ายของคอลเลกชันกลับมาก่อนหน้านี้ใน URL และเพิ่มส่วนWHEREคำสั่งแบบสอบถาม SQL ของคุณ (หรือสิ่งที่เทียบเท่าถ้าคุณไม่ได้ใช้ SQL) เพื่อที่จะส่งกลับองค์ประกอบเหล่านั้นเท่านั้น กุญแจสำคัญมากกว่าค่านี้หรือไม่?


4
นี่ไม่ใช่ข้อเสนอแนะที่ไม่ดี แต่เพียงเพราะคุณเรียงตามค่าไม่ได้หมายความว่ามันเป็น 'กุญแจ' นั่นคือไม่ซ้ำกัน
Chris Peacock

เผง เช่นในกรณีของฉันฟิลด์การเรียงลำดับเกิดขึ้นเป็นวันที่และอยู่ไกลจากที่ไม่ซ้ำกัน
Sat Thiru

19

อาจมีสองวิธีขึ้นอยู่กับตรรกะด้านเซิร์ฟเวอร์ของคุณ

วิธีที่ 1: เมื่อเซิร์ฟเวอร์ไม่ฉลาดพอที่จะจัดการสถานะวัตถุ

คุณสามารถส่งรหัสแคชที่ไม่ซ้ำกันของระเบียนไปยังเซิร์ฟเวอร์ได้เช่น ["id1", "id2", "id3", "id4", "id5", "id6", "id6", "id8", "id9", "id10"] และพารามิเตอร์บูลีนเพื่อทราบว่าคุณกำลังร้องขอบันทึกใหม่ (ดึงเพื่อรีเฟรช) หรือบันทึกเก่า (โหลดเพิ่มเติม)

เซิร์ฟเวอร์ของคุณควรรับผิดชอบในการส่งคืนเรกคอร์ดใหม่ (โหลดเร็กคอร์ดเพิ่มเติมหรือเร็กคอร์ดใหม่ผ่าน pull เพื่อรีเฟรช) รวมถึง id ของเร็กคอร์ดที่ถูกลบจาก ["id1", "id2", "id3", "id4", "id5", " id6" , "id7", "id8", "id9", "id10"]

ตัวอย่าง: - หากคุณกำลังขอโหลดมากขึ้นคำขอของคุณควรมีลักษณะดังนี้: -

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10"]
}

ตอนนี้สมมติว่าคุณกำลังขอบันทึกเก่า (โหลดเพิ่มเติม) และสมมติว่ามีการอัปเดตระเบียนโดย "บางคน" และ "id5" และ "id8" จากเซิร์ฟเวอร์ดังนั้นการตอบกลับของเซิร์ฟเวอร์ของคุณควรมีลักษณะดังนี้: -

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

แต่ในกรณีนี้หากคุณมีระเบียนแคชในท้องถิ่นจำนวนมากสมมติว่า 500 แล้วสตริงคำขอของคุณจะยาวเกินไปเช่นนี้: -

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10",………,"id500"]//Too long request
}

วิธีที่ 2: เมื่อเซิร์ฟเวอร์ฉลาดพอที่จะจัดการสถานะวัตถุตามวันที่

คุณสามารถส่ง id ของบันทึกแรกและบันทึกล่าสุดและเวลายุคก่อนหน้าคำขอ ด้วยวิธีนี้คำขอของคุณจะเล็กเสมอแม้ว่าคุณจะมีระเบียนแคชจำนวนมาก

ตัวอย่าง: - หากคุณกำลังขอโหลดมากขึ้นคำขอของคุณควรมีลักษณะดังนี้: -

{
        "isRefresh" : false,
        "firstId" : "id1",
        "lastId" : "id10",
        "last_request_time" : 1421748005
}

เซิร์ฟเวอร์ของคุณมีหน้าที่รับผิดชอบในการส่งคืน ID ของบันทึกที่ถูกลบซึ่งจะถูกลบหลังจาก last_request_time รวมถึงส่งคืนระเบียนที่อัปเดตหลังจาก last_request_time ระหว่าง "id1" และ "id10"

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

ดึงเพื่อรีเฟรช: -

ป้อนคำอธิบายรูปภาพที่นี่

โหลดมากขึ้น

ป้อนคำอธิบายรูปภาพที่นี่


14

อาจเป็นเรื่องยากที่จะหาแนวทางปฏิบัติที่ดีที่สุดเนื่องจากระบบที่มี API ส่วนใหญ่ไม่รองรับสถานการณ์นี้เนื่องจากเป็นขอบที่สุดขีดหรือโดยทั่วไปจะไม่ลบบันทึก (Facebook, Twitter) Facebook ระบุว่า "หน้า" แต่ละอันอาจไม่มีจำนวนผลลัพธ์ที่ขอเนื่องจากการกรองเสร็จหลังจากการแบ่งหน้า https://developers.facebook.com/blog/post/478/

หากคุณจำเป็นต้องรองรับเคสขอบนี้จริงๆคุณต้อง "จำ" ที่ค้างไว้ คำแนะนำ jandjorgensen เป็นเพียงเกี่ยวกับจุด แต่ฉันจะใช้เขตข้อมูลที่รับประกันว่าจะไม่ซ้ำกันเช่นคีย์หลัก คุณอาจต้องใช้มากกว่าหนึ่งช่อง

ติดตามการไหลของ Facebook คุณสามารถ (และควร) แคชหน้าเว็บที่ร้องขอไปแล้วและส่งคืนแถวที่ถูกลบซึ่งกรองออกหากพวกเขาร้องขอหน้าเว็บที่พวกเขาร้องขอไปแล้ว


2
นี่ไม่ใช่วิธีการแก้ปัญหาที่ยอมรับได้ มันใช้เวลาและหน่วยความจำมาก ข้อมูลที่ถูกลบทั้งหมดพร้อมกับข้อมูลที่ร้องขอจะต้องเก็บไว้ในหน่วยความจำซึ่งอาจไม่สามารถใช้งานได้เลยหากผู้ใช้คนเดียวกันไม่ได้ร้องขอรายการเพิ่มเติมอีก
Deepak Garg

3
ฉันไม่เห็นด้วย. เพียงแค่การรักษา ID ที่ไม่ซ้ำกันไม่ได้ใช้หน่วยความจำมาก คุณไม่ต้องเก็บรักษาข้อมูลไว้โดยไม่ จำกัด เวลาเพียงเพื่อ "เซสชัน" นี้เป็นเรื่องง่ายด้วย memcache เพียงกำหนดระยะเวลาหมดอายุ (เช่น 10 นาที)
เบรนต์ Baisley

หน่วยความจำราคาถูกกว่าความเร็วเครือข่าย / CPU ดังนั้นหากการสร้างหน้ามีราคาแพงมาก (ในแง่ของเครือข่ายหรือใช้ CPU มาก) ดังนั้นการแคชผลลัพธ์จึงเป็นวิธีที่ถูกต้อง @DeepakGarg
U Avalos

9

การแบ่งหน้าโดยทั่วไปเป็นการดำเนินการ "ผู้ใช้" และเพื่อป้องกันการโอเวอร์โหลดทั้งในคอมพิวเตอร์และสมองของมนุษย์โดยทั่วไปคุณจะให้เซตย่อย อย่างไรก็ตามแทนที่จะคิดว่าเราไม่ได้รับรายชื่อทั้งหมดอาจเป็นการดีกว่าที่จะถามว่ามีความสำคัญหรือไม่

หากจำเป็นต้องใช้มุมมองการเลื่อนสดที่แม่นยำ REST API ซึ่งเป็นคำขอ / การตอบกลับโดยทั่วไปจะไม่เหมาะสมสำหรับวัตถุประสงค์นี้ สำหรับสิ่งนี้คุณควรพิจารณา WebSockets หรือ HTML5 Server-Sent Events เพื่อให้ front front ของคุณทราบเมื่อต้องรับมือกับการเปลี่ยนแปลง

ตอนนี้หากมีความจำเป็นต้องได้รับสแนปชอตของข้อมูลฉันจะให้การเรียก API ที่ให้ข้อมูลทั้งหมดในคำขอเดียวโดยไม่มีการแบ่งหน้า ในใจคุณคุณจะต้องมีบางสิ่งที่จะทำการสตรีมเอาท์พุทโดยไม่ต้องโหลดลงในหน่วยความจำชั่วคราวหากคุณมีชุดข้อมูลขนาดใหญ่

สำหรับกรณีของฉันฉันกำหนดการเรียก API บางอย่างเพื่อให้ได้รับข้อมูลทั้งหมด (โดยหลักคือข้อมูลตารางอ้างอิง) คุณสามารถรักษาความปลอดภัยของ API เหล่านี้เพื่อไม่ให้เป็นอันตรายต่อระบบของคุณ


8

ตัวเลือก A: การแบ่งหน้าชุดคีย์ด้วยการประทับเวลา

เพื่อหลีกเลี่ยงข้อเสียของการแบ่งหน้าออฟเซ็ตที่คุณกล่าวถึงคุณสามารถใช้การแบ่งหน้าแบบคีย์เซ็ตได้ โดยทั่วไปเอนทิตีจะมีการประทับเวลาที่ระบุการสร้างหรือการปรับเปลี่ยนเวลา การประทับเวลานี้สามารถใช้สำหรับการแบ่งหน้า: เพียงแค่ส่งการประทับเวลาขององค์ประกอบสุดท้ายเป็นพารามิเตอร์แบบสอบถามสำหรับคำขอถัดไป เซิร์ฟเวอร์จะใช้การประทับเวลาเป็นเกณฑ์ตัวกรอง (เช่นWHERE modificationDate >= receivedTimestampParameter)

{
    "elements": [
        {"data": "data", "modificationDate": 1512757070}
        {"data": "data", "modificationDate": 1512757071}
        {"data": "data", "modificationDate": 1512757072}
    ],
    "pagination": {
        "lastModificationDate": 1512757072,
        "nextPage": "https://domain.de/api/elements?modifiedSince=1512757072"
    }
}

ด้วยวิธีนี้คุณจะไม่พลาดองค์ประกอบใด ๆ วิธีการนี้ควรจะดีพอสำหรับกรณีการใช้งานหลายกรณี อย่างไรก็ตามโปรดคำนึงถึงสิ่งต่อไปนี้:

  • คุณอาจเจอวนซ้ำไม่รู้จบเมื่อองค์ประกอบทั้งหมดของหน้าเดียวมีการประทับเวลาเดียวกัน
  • คุณสามารถส่งองค์ประกอบหลาย ๆ ครั้งไปยังลูกค้าเมื่อองค์ประกอบที่มีการประทับเวลาเดียวกันซ้อนทับสองหน้า

คุณสามารถทำให้ข้อเสียเหล่านั้นมีโอกาสน้อยลงโดยการเพิ่มขนาดหน้าและใช้การประทับเวลาด้วยความแม่นยำมิลลิวินาที

ตัวเลือก B: การแบ่งหน้าของชุดคีย์เพิ่มเติมด้วยโทเค็นต่อเนื่อง

ในการจัดการข้อเสียที่กล่าวถึงของการแบ่งหน้าชุดคีย์ปกติคุณสามารถเพิ่มการชดเชยให้กับการประทับเวลาและใช้สิ่งที่เรียกว่า "Continuation Token" หรือ "Cursor" offset คือตำแหน่งขององค์ประกอบที่สัมพันธ์กับองค์ประกอบแรกที่มีการประทับเวลาเดียวกัน โดยปกติโทเค็นจะมีรูปแบบTimestamp_Offsetดังนี้ มันถูกส่งไปยังลูกค้าในการตอบสนองและสามารถส่งกลับไปยังเซิร์ฟเวอร์เพื่อดึงหน้าถัดไป

{
    "elements": [
        {"data": "data", "modificationDate": 1512757070}
        {"data": "data", "modificationDate": 1512757072}
        {"data": "data", "modificationDate": 1512757072}
    ],
    "pagination": {
        "continuationToken": "1512757072_2",
        "nextPage": "https://domain.de/api/elements?continuationToken=1512757072_2"
    }
}

โทเค็น "1512757072_2" ชี้ไปที่องค์ประกอบสุดท้ายของหน้าและระบุ "ลูกค้าได้รับองค์ประกอบที่สองพร้อมกับการประทับเวลา 1512757072" ด้วยวิธีนี้เซิร์ฟเวอร์รู้ตำแหน่งที่จะดำเนินการต่อ

โปรดทราบว่าคุณต้องจัดการกับกรณีที่มีการเปลี่ยนแปลงองค์ประกอบระหว่างคำขอทั้งสอง โดยปกติจะทำโดยการเพิ่มการตรวจสอบการโทเค็น การตรวจสอบนี้จะถูกคำนวณมากกว่า ID ขององค์ประกอบทั้งหมดที่มีการประทับเวลานี้ Timestamp_Offset_Checksumดังนั้นเราจึงจบลงด้วยรูปแบบโทเค็นเช่นนี้

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการนี้ลองดูที่โพสต์บล็อก "การแบ่งหน้าเว็บ API ด้วยโทเค็นต่อเนื่อง " ข้อเสียเปรียบของวิธีนี้คือการใช้งานที่ยุ่งยากเนื่องจากมีหลายมุมที่ต้องคำนึงถึง นั่นเป็นเหตุผลว่าทำไมไลบรารี่อย่างต่อเนื่องจึงเป็นประโยชน์ (ถ้าคุณใช้ภาษา Java / a JVM) คำเตือน: ฉันเป็นผู้เขียนโพสต์และเป็นผู้เขียนร่วมของห้องสมุด


4

ฉันคิดว่าขณะนี้ API ของคุณตอบสนองจริงอย่างที่ควรจะเป็น ระเบียน 100 รายการแรกในหน้าตามลำดับวัตถุโดยรวมที่คุณดูแล คำอธิบายของคุณบอกว่าคุณกำลังใช้รหัสการสั่งซื้อเพื่อกำหนดลำดับของวัตถุของคุณสำหรับการให้เลขหน้า

ตอนนี้ในกรณีที่คุณต้องการให้หน้า 2 ควรเริ่มต้นจาก 101 และสิ้นสุดที่ 200 คุณต้องกำหนดจำนวนรายการในหน้าเป็นตัวแปรเนื่องจากอาจถูกลบ

คุณควรทำสิ่งต่าง ๆ เช่นรหัสปลอมด้านล่าง:

page_max = 100
def get_page_results(page_no) :

    start = (page_no - 1) * page_max + 1
    end = page_no * page_max

    return fetch_results_by_id_between(start, end)

1
ฉันเห็นด้วย. แทนที่จะสืบค้นด้วยหมายเลขระเบียน (ซึ่งไม่น่าเชื่อถือ) คุณควรสืบค้นด้วย ID เปลี่ยนการสืบค้นของคุณ (x, m) เพื่อหมายถึง "กลับไปที่ระเบียน m เรียงลำดับตาม ID ด้วย ID> x" จากนั้นคุณสามารถตั้งค่า x เป็น id สูงสุดจากผลลัพธ์การสืบค้นก่อนหน้า
John Henckel

ทรูทั้งการจัดเรียงในรหัสหรือหากคุณมีสาขาธุรกิจคอนกรีตบางอย่างที่จะเรียงลำดับเช่น CREATION_DATE ฯลฯ
mickeymoon

4

เพียงเพิ่มคำตอบนี้โดย Kamilk: https://www.stackoverflow.com/a/13905589

ขึ้นอยู่กับจำนวนชุดข้อมูลที่คุณใช้งาน ชุดข้อมูลขนาดเล็กทำงานได้อย่างมีประสิทธิภาพในการแบ่งหน้าออฟเซ็ตแต่ชุดข้อมูลเรียลไทม์ขนาดใหญ่จำเป็นต้องใช้การแบ่งหน้าเคอร์เซอร์

พบบทความที่ยอดเยี่ยมเกี่ยวกับวิธีที่Slackพัฒนาเลขหน้าของ api เนื่องจากมีชุดข้อมูลเพิ่มขึ้นเพื่ออธิบายถึงผลบวกและเชิงลบในทุกขั้นตอน: https://slack.engineering/evolving-api-pagination-at-slack-1c1f644f8e12


3

ฉันคิดมานานและหนักหน่วงเกี่ยวกับเรื่องนี้และในที่สุดก็จบลงด้วยการแก้ปัญหาที่ฉันจะอธิบายด้านล่าง มันเป็นขั้นตอนที่ซับซ้อนมาก แต่ถ้าคุณทำขั้นตอนนี้คุณจะได้สิ่งที่คุณต้องการจริงๆซึ่งเป็นผลลัพธ์ที่กำหนดไว้สำหรับคำขอในอนาคต

ตัวอย่างของรายการที่ถูกลบของคุณเป็นเพียงส่วนเล็ก ๆ จะเกิดอะไรขึ้นถ้าคุณกรองโดยcolor=blueมีบางคนเปลี่ยนสีของรายการระหว่างคำขอ กำลังดึงรายการทั้งหมดในลักษณะที่เพจได้อย่างน่าเชื่อถือเป็นไปไม่ได้ ... ถ้า ... เราใช้ประวัติการแก้ไข

ฉันได้ติดตั้งแล้วและมันก็ยากกว่าที่ฉันคาดไว้ นี่คือสิ่งที่ฉันทำ:

  • ฉันสร้างตารางเดียวchangelogsพร้อมคอลัมน์เพิ่ม ID อัตโนมัติ
  • เอนทิตีของฉันมีidฟิลด์ แต่นี่ไม่ใช่คีย์หลัก
  • เอนทิตีมีchangeIdฟิลด์ซึ่งเป็นทั้งคีย์หลักเช่นเดียวกับ foreign key เพื่อการเปลี่ยนแปลง
  • เมื่อใดก็ตามที่ผู้ใช้สร้างอัปเดตหรือลบบันทึกระบบจะแทรกเร็กคอร์ดใหม่changelogsคว้า ID และกำหนดให้กับเอนทิตีเวอร์ชันใหม่ซึ่งจะแทรกในฐานข้อมูล
  • ข้อความค้นหาของฉันเลือกการเปลี่ยนแปลงสูงสุด (จัดกลุ่มตาม ID) และเข้าร่วมด้วยตนเองเพื่อรับระเบียนทั้งหมดในเวอร์ชันล่าสุด
  • ตัวกรองถูกนำไปใช้กับระเบียนล่าสุด
  • ฟิลด์สถานะจะติดตามว่ามีการลบรายการหรือไม่
  • max changeId ถูกส่งคืนไปยังไคลเอ็นต์และเพิ่มเป็นพารามิเตอร์เคียวรีในคำร้องขอที่ตามมา
  • เนื่องจากมีการสร้างการเปลี่ยนแปลงใหม่เท่านั้นทุก ๆ รายการจึงchangeIdแสดงสแน็ปช็อตที่ไม่ซ้ำกันของข้อมูลที่สำคัญในขณะที่การเปลี่ยนแปลงถูกสร้างขึ้น
  • ซึ่งหมายความว่าคุณสามารถแคชผลลัพธ์ของการร้องขอที่มีพารามิเตอร์changeIdอยู่ในนั้นตลอดไป ผลลัพธ์จะไม่มีวันหมดอายุเพราะจะไม่มีการเปลี่ยนแปลง
  • นอกจากนี้ยังเปิดคุณสมบัติที่น่าตื่นเต้นเช่นการย้อนกลับ / ย้อนกลับการซิงค์แคชไคลเอ็นต์ ฯลฯ คุณลักษณะใด ๆ ที่ได้รับประโยชน์จากประวัติการเปลี่ยนแปลง

ฉันสับสน วิธีแก้ไขกรณีการใช้งานที่คุณกล่าวถึงนี้ (การเปลี่ยนแปลงฟิลด์สุ่มในแคชและคุณต้องการทำให้แคชใช้ไม่ได้)
U Avalos

สำหรับการเปลี่ยนแปลงใด ๆ ที่คุณทำด้วยตัวเองคุณเพียงแค่ดูการตอบสนอง เซิร์ฟเวอร์จะให้ข้อมูลการเปลี่ยนแปลงใหม่และคุณจะใช้สิ่งนั้นในคำขอถัดไปของคุณ สำหรับการเปลี่ยนแปลงอื่น ๆ (ทำโดยคนอื่น) คุณโพลการเปลี่ยนแปลงล่าสุดทุก ๆ ครั้งและถ้าสูงกว่าของคุณคุณรู้ว่ามีการเปลี่ยนแปลงที่โดดเด่น หรือคุณตั้งค่าระบบการแจ้งเตือนบางอย่าง (การสำรวจระยะยาวแบบพุชเซิร์ฟเวอร์, websockets) ที่แจ้งเตือนลูกค้าเมื่อมีการเปลี่ยนแปลงที่โดดเด่น
Stijn de Witt

0

อีกตัวเลือกหนึ่งสำหรับการแบ่งหน้าใน RESTful APIs คือการใช้ส่วนหัวของการเชื่อมโยงที่นำมาที่นี่ ตัวอย่างเช่น Github ใช้มันดังต่อไปนี้:

Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next",
  <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

ค่าที่เป็นไปได้สำหรับrelคือ: ครั้งแรกล่าสุดถัดไปก่อนหน้า แต่โดยใช้Linkส่วนหัวอาจเป็นไปไม่ได้ที่จะระบุtotal_count (จำนวนองค์ประกอบทั้งหมด)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.