Phân trang trong ứng dụng web REST


329

Đây là một cải cách chung hơn cho câu hỏi này (với việc loại bỏ các phần cụ thể của Rails)

Tôi không chắc chắn cách triển khai phân trang trên tài nguyên trong ứng dụng web RESTful. Giả sử rằng tôi có một tài nguyên được gọi products, bạn nghĩ cách nào sau đây là cách tiếp cận tốt nhất và tại sao:

1. Chỉ sử dụng chuỗi truy vấn

ví dụ. http://application/products?page=2&sort_by=date&sort_how=asc
Vấn đề ở đây là tôi không thể sử dụng bộ nhớ đệm toàn trang và URL cũng không rõ ràng và dễ nhớ.

2. Sử dụng các trang làm tài nguyên và chuỗi truy vấn để sắp xếp

ví dụ. http://application/products/page/2?sort_by=date&sort_how=asc
Trong trường hợp này, vấn đề được nhìn thấy là đó http://application/products/pages/1không phải là một tài nguyên duy nhất vì việc sử dụng sort_by=pricecó thể mang lại một kết quả hoàn toàn khác tôi vẫn không thể sử dụng bộ đệm ẩn trang.

3. Sử dụng các trang làm tài nguyên và phân đoạn URL để sắp xếp

ví dụ. http://application/products/by-date/page/2
Cá nhân tôi thấy không có vấn đề gì khi sử dụng phương pháp này, nhưng ai đó đã cảnh báo tôi rằng đây không phải là cách hay (anh ấy không đưa ra lý do, vì vậy nếu bạn biết tại sao nó không được khuyến nghị, vui lòng cho tôi biết)

Bất kỳ đề xuất, ý kiến, phê bình đều được chào đón. Cảm ơn.


34
Đâ là một câu hỏi tuyệt vời.
Người giữ Iain

7
Câu hỏi thưởng: làm thế nào để mọi người thường chỉ định kích thước trang?
Heiko Rupp

Đừng quên các tham số Ma trận w3.org/DesignIssues/MatrixURIs.html
CMCDragonkai

Câu trả lời:


66

Tôi nghĩ vấn đề với phiên bản 3 là vấn đề "quan điểm" hơn - bạn có thấy trang này là tài nguyên hoặc sản phẩm trên trang không.

Nếu bạn xem trang là tài nguyên thì đó là một giải pháp hoàn toàn tốt, vì truy vấn cho trang 2 sẽ luôn mang lại trang 2.

Nhưng nếu bạn thấy các sản phẩm trên trang là tài nguyên bạn gặp phải vấn đề thì các sản phẩm trên trang 2 có thể thay đổi (sản phẩm cũ đã bị xóa hoặc bất cứ điều gì), trong trường hợp này, URI không phải lúc nào cũng trả lại cùng một tài nguyên.

Ví dụ: Một khách hàng lưu trữ một liên kết đến trang danh sách sản phẩm X, lần tiếp theo liên kết được mở sản phẩm được đề cập có thể không còn trên trang X.


6
Chà, nhưng nếu bạn xóa thứ gì đó thì không nên có thứ gì khác trên cùng một URI. Nếu bạn xóa tất cả các sản phẩm của trang X - trang X có thể vẫn hợp lệ nhưng hiện có các sản phẩm từ trang X + 1. Vì vậy, URI cho trang X đã trở thành URI cho trang X + 1 nếu bạn thấy nó trong "chế độ xem tài nguyên sản phẩm ".
Fionn

1
> Nếu bạn xem trang là tài nguyên thì đó là một giải pháp hoàn toàn tốt, vì truy vấn cho trang 2 sẽ luôn mang lại trang 2. Nó thậm chí còn có ý nghĩa? Cùng một URL (bất kỳ URL nào đề cập đến trang 2) sẽ luôn mang lại trang 2 bất kể bạn là tài nguyên nào.
temoto

2
Xem trang dưới dạng tài nguyên có lẽ nên giới thiệu POST / foo / page để tạo một trang mới, phải không?
temoto

18
Câu trả lời của bạn trôi chảy đến "giải pháp đúng là 1", nhưng không nêu rõ.
temoto

2
Trong tâm trí của tôi, trang là một khái niệm nổi và không liên quan đến tên miền cơ bản. Và do đó không nên được coi là một tài nguyên. Tôi có nghĩa là nổi theo nghĩa là nó trôi chảy, rằng khái niệm trang thay đổi theo bối cảnh; một người dùng API của bạn có thể là một ứng dụng di động, chỉ có thể tiêu thụ 2 sản phẩm trên mỗi trang, trong khi người dùng còn lại là một ứng dụng máy có thể tiêu thụ toàn bộ danh sách chết tiệt. Nói tóm lại, trang là "đại diện" của thực thể tên miền cơ bản (sản phẩm) và không nên được đưa vào như một phần của URL; chỉ như một tham số truy vấn.
Kingz

106

Tôi đồng ý với Fionn, nhưng tôi sẽ tiến thêm một bước và nói rằng với tôi Trang không phải là tài nguyên, đó là tài sản của yêu cầu. Điều đó khiến tôi chỉ chọn tùy chọn 1 chuỗi truy vấn. Nó chỉ cảm thấy đúng. Tôi thực sự thích cách Twitter API được cấu trúc một cách yên tĩnh. Không quá đơn giản, không quá phức tạp, tài liệu tốt. Dù tốt hay xấu thì đó là thiết kế "đi đến" của tôi khi tôi ở trên hàng rào để làm một việc gì đó theo cách này so với cách khác.


28
+1: chuỗi truy vấn không phải là định danh tài nguyên hạng nhất; họ chỉ cần làm rõ để đặt hàng và nhóm tài nguyên.
S.Lott

1
@ S.Lott Yêu cầu tài nguyên. Những gì bạn gọi là "tài nguyên hạng nhất" được định nghĩa là các giá trị bằng cách Fielding trong phần 5.2.1.1 của luận án . Hơn nữa, trong cùng một phần, Fielding đưa ra Bản sửa đổi mới nhất của tệp mã nguồn làm ví dụ về tài nguyên. Làm thế nào đó có thể là một tài nguyên nhưng 10 sản phẩm mới nhất là "thuộc tính của yêu cầu đối với tài nguyên sản phẩm"? Tôi hiểu rằng quan điểm của bạn thực tế hơn, nhưng tôi nghĩ rằng nó ít RESTful hơn.
edsioufi

Lưu ý rằng nhận xét của tôi không có nghĩa là tôi không đồng ý với việc lựa chọn sử dụng chuỗi truy vấn qua URL: cả hai đều là giải pháp khả thi miễn là API được điều khiển theo hướng hypermedia, như @RichApodaca đã đề cập trong câu trả lời của anh ấy. Tôi chỉ chỉ ra rằng Trang nên được coi là một tài nguyên theo quan điểm REST.
edsioufi

37

HTTP có tiêu đề Phạm vi tuyệt vời cũng phù hợp để phân trang. Bạn có thể gửi

Range: pages=1

chỉ có trang đầu tiên Điều đó có thể buộc bạn phải suy nghĩ lại về một trang. Có thể khách hàng muốn một loạt các mặt hàng khác nhau. Tiêu đề phạm vi cũng hoạt động để khai báo một đơn đặt hàng:

Range: products-by-date=2009_03_27-

để có được tất cả các sản phẩm mới hơn ngày đó hoặc

Range: products-by-date=0-2009_11_30

để có được tất cả các sản phẩm cũ hơn ngày đó. '0' có lẽ không phải là giải pháp tốt nhất, nhưng RFC dường như muốn một cái gì đó để bắt đầu phạm vi. Có thể có các trình phân tích cú pháp HTTP được triển khai mà sẽ không phân tích đơn vị = -range_end.

Nếu các tiêu đề không phải là một tùy chọn (chấp nhận được), tôi nghĩ giải pháp đầu tiên (tất cả trong chuỗi truy vấn) là một cách để xử lý các trang. Nhưng xin vui lòng, bình thường hóa các chuỗi truy vấn (sắp xếp (key = value) theo thứ tự bảng chữ cái). Điều này giải quyết vấn đề phân biệt "? A = 1 & b = x" và "? B = x & a = 1".


34
Các tiêu đề có thể trông đẹp mắt ngay từ cái nhìn đầu tiên, nhưng chúng không cho phép chia sẻ trang (ví dụ: bằng cách sao chép url). Vì vậy, đối với yêu cầu ajax, chúng có thể là một giải pháp tốt (vì các trang được sửa đổi bởi ajax không thể được chia sẻ ở trạng thái hiện tại của chúng), nhưng tôi sẽ không sử dụng chúng để phân trang thông thường.
Markus

3
Và tiêu đề Range chỉ dành cho phạm vi byte. Xem [thông số tiêu đề HTTP] ( w3.org/Prot Protocol / rfc2616 / rfc2616-sec14.html ), phần 14,35.
Chris Westin

16
@ChrisWestin w3.org/Prot Protocol / rfc2616 / rfc2616-sec3.html # sec3.12 HTTP / 1.1 sử dụng các đơn vị phạm vi trong các trường tiêu đề Phạm vi (phần 14,35) và Phạm vi nội dung (phần 14.16). range-unit = bytes-unit | other-range-unit Có thể bạn đang đề cập đến The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units.Điều đó không giống như tuyên bố của bạn.
temoto

1
@Markus Tôi không thể tưởng tượng trường hợp sử dụng khi bạn đang chia sẻ tài nguyên api còn lại :)
JakubKnejzlik

@JakubKnejzlik Chia sẻ không phải là vấn đề, nhưng việc sử dụng các tiêu đề HTTP để phân trang sẽ ngăn việc sử dụng các liên kết HATEOAS để phân trang.
xarx

25

Tùy chọn 1 có vẻ tốt nhất, đến mức mà ứng dụng của bạn xem phân trang là một kỹ thuật để tạo ra một chế độ xem khác nhau cho cùng một tài nguyên.

Phải nói rằng, lược đồ URL tương đối không đáng kể. Nếu bạn đang thiết kế ứng dụng của mình theo định hướng siêu văn bản (vì tất cả các ứng dụng REST phải theo định nghĩa), thì máy khách của bạn sẽ không tự mình xây dựng bất kỳ URI nào. Thay vào đó, ứng dụng của bạn sẽ cung cấp các liên kết cho khách hàng và khách hàng sẽ theo dõi họ.

Một loại liên kết mà khách hàng của bạn có thể cung cấp là liên kết phân trang.

Tác dụng phụ dễ chịu của tất cả những điều này là ngay cả khi bạn thay đổi suy nghĩ về cấu trúc URI phân trang và thực hiện một cái gì đó hoàn toàn khác vào tuần tới, khách hàng của bạn có thể tiếp tục làm việc mà không cần sửa đổi gì.


3
Rất nhắc nhở về việc sử dụng hypermedia như các liên kết trong các dịch vụ web REST.
Paul D. Eden

11

Tôi đã luôn sử dụng kiểu tùy chọn 1. Bộ nhớ đệm không phải là vấn đề đáng lo ngại vì dữ liệu thay đổi thường xuyên trong trường hợp của tôi. Nếu bạn cho phép kích thước của trang có thể định cấu hình thì một lần nữa dữ liệu không thể được lưu vào bộ nhớ cache.

Tôi không tìm thấy url khó nhớ hoặc ô uế. Đối với tôi đây là một sử dụng tốt các tham số truy vấn. Tài nguyên rõ ràng là một danh sách các sản phẩm và các tham số truy vấn chỉ cho biết cách bạn muốn danh sách được hiển thị - được sắp xếp và trang nào.


1
1 Tôi nghĩ rằng bạn là đúng và tôi sẽ đi với các thông số truy vấn (tùy chọn 1)
andi

"Tôi không tìm thấy URL khó nhớ". Quan sát này là vô ích trong các ứng dụng REST, vì những ứng dụng này thường chỉ có một dấu trang duy nhất ... Nếu người dùng (hoặc ứng dụng khách) cố gắng "nhớ" URL, đây là một dấu hiệu tốt cho thấy API không được nghỉ ngơi.
edsioufi

8

Điều kỳ lạ là không ai chỉ ra rằng Tùy chọn 3 có các tham số theo một thứ tự cụ thể. http // ứng dụng / sản phẩm / Ngày / Giảm dần / Tên / Tăng dần / trang / 2http // ứng dụng / sản phẩm / Tên / Tăng dần / Ngày / Giảm dần / trang / 2

đang trỏ đến cùng một tài nguyên, nhưng có các url hoàn toàn khác nhau.

Đối với tôi, Lựa chọn 1 có vẻ dễ chấp nhận nhất, vì nó phân tách rõ ràng "Điều tôi muốn""Tôi muốn như thế nào" (Nó thậm chí còn có dấu hỏi giữa chúng lol). Bộ nhớ đệm toàn trang có thể được triển khai bằng URL đầy đủ (Tất cả các tùy chọn sẽ chịu cùng một vấn đề).

Với cách tiếp cận Tham số trong URL, lợi ích duy nhất là URL sạch. Mặc dù bạn phải nghĩ ra một số cách để mã hóa các tham số và giải mã chúng một cách dễ dàng. Tất nhiên bạn có thể đi với URLencode / giải mã, nhưng nó sẽ làm cho url trở nên xấu xí một lần nữa :)


1
Đó là hai thứ tự khác nhau. Các loại đầu tiên sắp xếp theo ngày giảm dần, và chỉ phá vỡ các mối quan hệ theo tên tăng dần; loại thứ hai theo tên tăng dần, và chỉ phá vỡ mối quan hệ theo ngày giảm dần.
Imran Rashid

Trong thực tế, hai URL ví dụ được đưa ra ở đây không chỉ khác nhau bằng cách viết mà còn có ý nghĩa. Kể từ khi biểu thị một đường dẫn, không có đảm bảo nào được đưa ra rằng bạn tìm thấy điều tương tự khi rẽ trái trước và phải sau hoặc ngược lại. Đã nói điều này, sắp xếp các tham số như các phần của đường dẫn URL có lợi thế chính thức so với các tham số URL có thể trao đổi về mặt giao tiếp mà không thay đổi ý nghĩa tổng thể, nhưng thực sự phải chịu các bẫy mã hóa như đã nói ở đây.
Christian Gosch 10/03/2015

7

Tôi muốn sử dụng các tham số truy vấn bù và giới hạn.

offset : cho chỉ mục của mục trong bộ sưu tập.

giới hạn : cho số lượng mặt hàng.

Khách hàng có thể tiếp tục cập nhật phần bù như sau

offset = offset + limit

cho trang tiếp theo.

Đường dẫn được coi là định danh tài nguyên. Và một trang không phải là tài nguyên mà là tập hợp con của bộ sưu tập tài nguyên. Vì phân trang nói chung là một yêu cầu GET, các tham số truy vấn phù hợp nhất cho phân trang hơn là các tiêu đề.

Tham khảo: https://metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page


5

Tìm kiếm các thực tiễn tốt nhất tôi đã xem qua trang web này:

http://www.restapitutorial.com

Trong trang tài nguyên có một liên kết để tải xuống .pdf có chứa các thực tiễn tốt nhất REST hoàn chỉnh được đề xuất bởi tác giả. Trong đó trong số những thứ khác có một phần về phân trang.

Tác giả đề nghị thêm hỗ trợ cho cả hai bằng cách sử dụng tiêu đề Phạm vi và sử dụng tham số chuỗi truy vấn.

Yêu cầu

Ví dụ tiêu đề HTTP:

Range: items=0-24

Ví dụ về tham số chuỗi truy vấn:

GET http://api.example.com/resources?offset=0&limit=25

Trong đó offset là số mục bắt đầu và giới hạn là số mục tối đa được trả về.

Phản ứng

Phản hồi phải bao gồm tiêu đề Phạm vi Nội dung cho biết có bao nhiêu mục đang được trả lại và tổng số mục tồn tại chưa được truy xuất

Ví dụ tiêu đề HTTP:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

Trong .pdf có một số gợi ý khác cho các trường hợp cụ thể hơn.


4

Tôi hiện đang sử dụng một lược đồ tương tự như thế này trong các ứng dụng ASP.NET MVC của mình:

ví dụ http://application/products/by-date/page/2

cụ thể là: http://application/products/Date/Ascending/3

Tuy nhiên, tôi không thực sự hài lòng với việc bao gồm phân trang và sắp xếp thông tin trong tuyến đường theo cách này.

Danh sách các mặt hàng (sản phẩm trong trường hợp này) là có thể thay đổi. tức là lần sau khi ai đó quay lại url bao gồm các tham số phân trang và sắp xếp, kết quả họ nhận được có thể đã thay đổi. Vì vậy, ý tưởng về http://application/products/Date/Ascending/3một url duy nhất trỏ đến một bộ sản phẩm được xác định, không thay đổi sẽ bị mất.


1
Vấn đề đầu tiên, với việc sắp xếp trên nhiều cột, theo tôi, áp dụng cho cả 3 phương pháp. Vì vậy, nó không thực sự là một pro / con cho bất kỳ trong số họ. Về vấn đề thứ hai: không thể xảy ra với bất kỳ tài nguyên nào? Một sản phẩm, ví dụ, cũng có thể được chỉnh sửa / xóa.
andi

Tôi nghĩ rằng việc sắp xếp trên nhiều cột thực sự là một 'con' cho cả 3 phương thức vì url trở nên lớn hơn và không thể quản lý hơn - do đó, một lý do tôi đang xem xét chuyển sang hình thành các tham số trang / sắp xếp dựa trên. Đối với vấn đề thứ hai, tôi nghĩ rằng có một sự khác biệt về khái niệm cơ bản giữa một mã định danh liên tục duy nhất như id sản phẩm hơn là danh sách các sản phẩm nhất thời. Đối với các sản phẩm đã xóa, một thông báo, ví dụ: 'Sản phẩm đó không tồn tại trong hệ thống' cho bạn biết điều gì đó cụ thể về sản phẩm đó.
Steve Willcock

1
Loại bỏ tất cả các thông tin phân trang và sắp xếp từ tuyến đường là tốt. Và đẩy nó vào tham số POST là xấu. Xin chào? Câu hỏi là về REST. Chúng tôi không sử dụng POST chỉ để làm cho URL ngắn hơn trong REST. Động từ có ý nghĩa.
temoto

1
Cá nhân, tôi sẽ không sử dụng các tham số biểu mẫu cho một truy vấn vì nó hầu như sẽ yêu cầu phương thức POST hoặc PUT HTTP (vì hiện tại có một phần thân trong yêu cầu). GET có vẻ như tôi thích phương pháp thích hợp hơn để sử dụng vì cả POST và PUT đều ngụ ý sửa đổi tài nguyên. Do đó, tôi sẽ bổ sung thêm các tham số truy vấn vào URL khi cần sắp xếp theo nhiều cột.
Paul D. Eden

1

Tôi có xu hướng đồng ý với slf rằng "trang" không thực sự là một tài nguyên. Mặt khác, tùy chọn 3 sạch hơn, dễ đọc hơn và người dùng có thể dễ dàng đoán ra hơn và thậm chí có thể gõ ra nếu cần thiết. Tôi bị giằng xé giữa tùy chọn 1 và 3, nhưng không thấy lý do nào để không sử dụng tùy chọn 3.

Ngoài ra, trong khi chúng trông đẹp mắt, một nhược điểm của việc sử dụng các tham số ẩn, như ai đó đã đề cập, thay vì các chuỗi truy vấn hoặc phân đoạn URL là người dùng không thể đánh dấu hoặc liên kết trực tiếp đến một trang cụ thể. Điều đó có thể hoặc không thể là một vấn đề tùy thuộc vào ứng dụng, nhưng chỉ cần một cái gì đó để nhận biết.


1
Về việc bạn đề cập đến việc dễ đoán hơn, điều này không thành vấn đề. Nếu xây dựng API hypermedia, người dùng không bao giờ nên đoán URI.
JR Garcia

0

Tôi đã sử dụng giải pháp 3 trước đây (tôi viết RẤT NHIỀU ứng dụng django). Và tôi không nghĩ rằng có bất cứ điều gì sai với nó. Nó cũng hào phóng như hai cái kia (trong trường hợp bạn cần phải thực hiện một số thao tác cạo hàng loạt hoặc tương tự) và nó trông sạch sẽ hơn. Ngoài ra, người dùng của bạn có thể đoán các url (nếu là ứng dụng công khai) và mọi người thích có thể đi trực tiếp đến nơi họ muốn và việc đoán url cảm thấy có sức mạnh.


0

Tôi sử dụng trong các dự án của mình các url sau:

http://application/products?page=2&sort=+field1-field2

có nghĩa là - "hãy cho tôi trang thứ hai được sắp xếp tăng dần theo trường1 và sau đó giảm dần theo trường2". Hoặc nếu tôi cần linh hoạt hơn nữa, tôi sử dụng:

http://application/products?skip=20&limit=20&sort=+field1-field2

0

Tôi sử dụng trong các mẫu sau để có được bản ghi trang tiếp theo. http: // application / sản phẩm? lastRecordKey =? & pageSize = 20 & sort = ASC

RecordKey là cột của bảng chứa giá trị tuần tự trong DB. Điều này được sử dụng để chỉ tìm nạp một dữ liệu trang tại DB. pageSize được sử dụng để xác định số lượng bản ghi cần tìm nạp. sort được sử dụng để sắp xếp bản ghi theo thứ tự tăng dần hoặc giảm dần.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.