Пагинація у веб-програмі REST


329

Це більш загальне формулювання цього питання (з усуненням конкретних частин Рейки)

Я не впевнений, як реалізувати сторінку на ресурсі в веб-додатку RESTful. Якщо припустити, що у мене є ресурс products, який називається , який із перелічених нижче ви вважаєте найкращим підходом і чому:

1. Використання лише рядків запиту

напр. http://application/products?page=2&sort_by=date&sort_how=asc
Проблема тут полягає в тому, що я не можу використовувати кешування на всій сторінці, а також URL-адреса не дуже чиста і легко запам’ятовується.

2. Використання сторінок як ресурсів та рядків запиту для сортування

напр. http://application/products/page/2?sort_by=date&sort_how=asc
У цьому випадку проблема, яка бачиться, полягає в тому, що http://application/products/pages/1це не унікальний ресурс, оскільки використання sort_by=priceможе дати зовсім інший результат, і я досі не можу використовувати кешування сторінок.

3. Використання сторінок як ресурсів та сегменту URL для сортування

напр. http://application/products/by-date/page/2
Я особисто не бачу жодних проблем у використанні цього методу, але хтось попередив мене, що це не дуже вдалий шлях (він не пояснив причину, тому якщо ви знаєте, чому це не рекомендується, будь ласка, дайте мені знати)

Будь-які пропозиції, думки, критики є більш ніж вітаються. Дякую.


34
Це чудове питання.
Ієн Холдер

7
Питання про бонус: як люди зазвичай визначають розміри сторінок?
Хайко Рупп

Не забувайте про параметри матриці w3.org/DesignIssues/MatrixURIs.html
CMCDragonkai

Відповіді:


66

Я думаю, що проблема з версією 3 - це скоріше проблема "точки зору" - чи бачите ви її як ресурс чи продукти на сторінці.

Якщо ви бачите сторінку як ресурс, це ідеально прекрасне рішення, оскільки запит на сторінку 2 завжди буде сторінкою 2.

Але якщо ви бачите продукти на сторінці як ресурс, у вас виникає проблема, що продукти на сторінці 2 можуть змінитися (старі продукти видалені чи що завгодно), в цьому випадку URI не завжди повертає той самий ресурс.

Напр. Клієнт зберігає посилання на сторінку X із списком продуктів, наступного разу, коли посилання відкриється, продукт, про який йде мова, може бути більше не на сторінці X.


6
Добре, але якщо ви видалите щось, на цьому ж URI не повинно бути нічого іншого. Якщо ви видалите всі продукти сторінки X - сторінка X все ще може бути дійсною, але тепер містить продукти зі сторінки X + 1. Отже, URI для сторінки X став URI для сторінки X + 1, якщо ви бачите це у "ресурсі продукту" ".
Фіонн

1
> Якщо ви бачите сторінку як ресурс, це ідеально вдале рішення, оскільки запит на сторінку 2 завжди буде давати сторінку 2. Чи це навіть має сенс? Один і той же URL-адреса (будь-яка URL-адреса, що згадує сторінку 2) завжди отримуватиме сторінку 2 незалежно від того, що ви є ресурсом.
темто

2
Якщо бачити сторінку як ресурс, мабуть, слід запровадити POST / foo / сторінку для створення нової сторінки, правда?
темто

18
Ваша відповідь чітко йде до "правильного рішення - це 1", але не зазначає його.
темто

2
На мій погляд, сторінка - це плаваюче поняття, яке не пов'язане з базовим доменом. А тому не слід розглядати як ресурс. Я маю на увазі плаваюче в тому сенсі, що це текуче, що концепція сторінки змінюється контекстом; один користувач вашого API може бути мобільним додатком, який може споживати лише 2 продукти на сторінку, а інший - це машинне додаток, яке може споживати весь проклятий список. Коротше кажучи, сторінка - це "представлення" базового об'єкта домену (продукту) і не повинна включатися як частина URL-адреси; лише як параметр запиту.
Кінгз

106

Я погоджуюся з Fionn, але я піду на крок далі і скажу, що для мене Сторінка - це не ресурс, це властивість запиту. Це змушує мене обрати лише варіант запиту 1. Це просто правильно. Мені дуже подобається, як спокійно структурується API Twitter . Не надто простий, не надто складний, добре задокументований. На краще чи гірше - це мій проект "піти на", коли я на огорожі, роблячи щось в один бік проти іншого.


28
+1: рядки запитів не є першокласними ідентифікаторами ресурсів; вони просто роз'яснення для впорядкування та групування ресурсу.
С.Лотт

1
@ S.Lott Запит - це ресурс. Те, що ви називаєте "першокласними ресурсами", визначається як значення Філдінг у розділі 5.2.1.1 його дисертації . Крім того, у цьому ж розділі Fielding наводить останню редакцію файлу вихідного коду як приклад ресурсу. Як це може бути ресурсом, але останні 10 продуктів - це "властивості запиту на ресурсі продуктів"? Я розумію, що ваш погляд є більш практичним, але я вважаю, що він менш RESTful.
edsioufi

Зауважте, що мій коментар не означає, що я не згоден з вибором використання рядків запитів над URL-адресами: обидва є життєздатними рішеннями, доки API керується гіпермедіацією, про що @RichApodaca згадував у своїй відповіді. Я просто вказую, що Сторінку слід розглядати як ресурс з точки зору REST.
edsioufi

37

HTTP має чудовий заголовок діапазону, який підходить і для сторінки. Ви можете відправити

Range: pages=1

мати лише першу сторінку. Це може змусити вас переосмислити, що таке сторінка. Можливо, клієнт хоче іншого асортименту. Заголовок діапазону також працює для оголошення замовлення:

Range: products-by-date=2009_03_27-

щоб отримати всі продукти новіші за цю дату або

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

щоб отримати всі продукти старші за цю дату. "0", мабуть, не найкраще рішення, але RFC, здається, хоче щось для запуску діапазону. Можливо, буде розгорнуто HTTP-парсер, який не розбиратиме одиниці = -range_end.

Якщо заголовки не є (прийнятним) варіантом, я вважаю, що перше рішення (все в рядку запиту) - це спосіб обробляти сторінки. Але, будь ласка, нормалізуйте рядки запитів (сортування (ключ = значення) пар в алфавітному порядку). Це вирішує задачу диференціації "? A = 1 & b = x" і "? B = x & a = 1".


34
заголовки можуть виглядати приємно на перший погляд, але вони забороняють ділитися сторінкою (наприклад, копіюючи URL). Отже, для запиту ajax вони можуть бути гарним рішенням (оскільки сторінки, модифіковані ajax, не можуть бути загальнодоступними в їх поточному стані), але я б не використовував їх для регулярної сторінки.
Маркус

3
І заголовок Range призначений лише для байтових діапазонів. Див. [Специфікація заголовків HTTP] ( w3.org/Protocols/rfc2616/rfc2616-sec14.html ), розділ 14.35.
Кріс Вестін

16
@ChrisWestin w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 HTTP / 1.1 використовує одиниці діапазону у полях заголовка діапазону (розділ 14.35) та вмісту (розділ 14.16). range-unit = bytes-unit | other-range-unit Можливо, ви маєте на увазі The only range unit defined by HTTP/1.1 is "bytes". HTTP/1.1 implementations MAY ignore ranges specified using other units.Це не те саме, що ваше твердження.
темто

1
@Markus Я не можу уявити випадок використання, коли ви ділитесь ресурсом api відпочинку :)
JakubKnejzlik

@JakubKnejzlik Обмін не є проблемою, але використання заголовків HTTP для підкачки не дозволяє використовувати посилання HATEOAS для підкачки.
xarx

25

Варіант 1 видається найкращим, наскільки ваша програма розглядає сторінкусторінку як техніку створення іншого погляду одного і того ж ресурсу.

Сказавши це, схема URL є відносно незначною. Якщо ви проектуєте свою програму на основі гіпертексту (як і всі REST-програми повинні бути визначені за визначенням), то ваш клієнт не буде будувати ніяких URI самостійно. Натомість ваша програма надаватиме посилання на клієнта, і клієнт буде слідувати за ними.

Один з типів посилань, які може надати ваш клієнт, - це посилання на сторінку.

Приємним побічним ефектом усього цього є те, що навіть якщо ви передумаєте про структуру URI сторінки розширення та впровадите щось зовсім інше на наступному тижні, ваші клієнти можуть продовжувати працювати без будь-яких змін.


3
Приємне нагадування про використання гіпермедіатичних посилань у веб-службах REST.
Пол Д. Іден

11

Я завжди використовував стиль варіанти 1. Кешування не викликає проблем, оскільки в моєму випадку дані часто змінюються. Якщо ви дозволяєте налаштувати розмір сторінки, дані знову не можуть бути кешовані.

Я не вважаю URL важким для запам'ятовування чи нечистим. Для мене це прекрасне використання параметрів запиту. Ресурс - це чітко перелік продуктів, а параметри запиту - це лише те, як ви хочете, щоб список відображався - відсортовано та на якій сторінці.


1
+1 Я думаю, ти маєш рацію, і я перейду з параметрами запиту (варіант 1)
andi

"Мені не важко запам'ятати URL-адресу". Це спостереження марне в додатках REST, оскільки вони, як правило, мають лише одну єдину закладку ... Якщо користувач (або клієнтська програма) намагається "запам'ятати" URL-адресу, це є хорошим знаком того, що API не є спокійним.
edsioufi

8

Дивно, що ніхто не вказав, що варіант 3 має параметри в певному порядку. http // заявка / продукція / дата / Зростання / Ім'я / Зростання / сторінка / 2 та http // додаток / продукти / Ім'я / Зростання / Дата / Зростання / Сторінка / 2

вказують на один і той же ресурс, але мають абсолютно різні URL-адреси.

Для мене варіант 1 видається найбільш прийнятним, оскільки він чітко розмежовує "Що я хочу" та "Як я хочу" (у нього навіть знак запитання між ними, хай). Кешування на повній сторінці може бути реалізовано за допомогою повної URL-адреси (усі параметри все одно матимуть однакову проблему).

При підході параметрів в URL-адресі єдиною перевагою є чиста URL-адреса. Хоча вам доведеться придумати якийсь спосіб кодування параметрів і без втрат розшифрувати їх. Звичайно, ви можете піти з URL-кодом / декодуванням, але це знову зробить URL-адресою негарним :)


1
Це два різних порядку. Перший сортує за спаданням дати та розриває зв'язки лише за зростанням імені; другий сортує за назвою за зростанням і розриває зв'язки лише за датою спадання.
Імран Рашид

Насправді два наведені тут приклади URL відрізняються не лише написанням, але й значенням. Оскільки позначається шлях, не надається гарантія, що ви виявите те саме, коли повертаєте ліворуч і праворуч згодом або навпаки. Сказавши це, параметри сортування за частинами шляху URL мають формальні переваги перед параметрами URL, які повинні бути комутаційно обмінні, не змінюючи загального значення, але дійсно страждають від пасток кодування, як сказано тут.
Крістіан Гош

7

Я вважаю за краще зміщення та обмеження параметрів запиту.

зміщення : для індексу елемента в колекції.

ліміт : для кількості предметів.

Клієнт може просто оновлювати компенсацію наступним чином

offset = offset + limit

для наступної сторінки.

Шлях вважається ідентифікатором ресурсу. І сторінка - це не ресурс, а підмножина колекції ресурсів. Оскільки пагинація, як правило, є GET-запитом, параметри запитів найкраще підходять для сторінки, а не заголовків.

Довідка: https://metamug.com/article/rest-api-developers-dilemma.html#Requesting-the-next-page


5

Шукаючи кращих практик, я натрапив на цей сайт:

http://www.restapitutorial.com

На сторінці ресурсів є посилання на завантаження .pdf, що містить повний передовий досвід REST, запропонований автором. У якому серед іншого є розділ про пагінацію.

Автор пропонує додати підтримку як за допомогою заголовка Range, так і за допомогою параметрів рядка запиту.

Запит

Приклад заголовка HTTP:

Range: items=0-24

Приклад параметрів рядка запиту:

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

Якщо зсув - це номер початкового пункту, а межа - максимальна кількість елементів, які потрібно повернути.

Відповідь

Відповідь має містити заголовок вмісту, який вказує, скільки елементів повертається і скільки загальних елементів ще потрібно отримати.

Приклади HTTP-заголовка:

Content-Range: items 0-24/66

Content-Range: items 40-65/*

У .pdf є деякі інші пропозиції щодо більш конкретних випадків.


4

Наразі я використовую схему, подібну до цієї, у своїх додатках ASP.NET MVC:

напр http://application/products/by-date/page/2

конкретно це: http://application/products/Date/Ascending/3

Однак я не дуже задоволений тим, що таким чином включати підкачку та сортування інформації в маршрут.

Список предметів (товарів у цьому випадку) є змінним. тобто наступного разу, коли хтось повернеться до URL-адреси, що включає параметри підкачки та сортування, результати, які вони отримують, можливо, змінилися. Так ідея http://application/products/Date/Ascending/3як унікальний URL, який вказує на визначений, незмінний набір продуктів, втрачається.


1
Перший випуск з сортуванням по декількох стовпцях стосується всіх трьох, на мою думку, методів. Тож це насправді не про / не для жодного з них. Щодо другого питання: чи не може це відповідати жодному ресурсу? Наприклад, продукт можна також редагувати / видаляти.
andi

Я думаю, що сортування за декількома стовпцями насправді є «con» для всіх 3-х методів, оскільки URL-адреса стає просто більшою та некерованою - отже, одна з причин я розглядаю можливість переходу до параметрів сторінки на основі сторінки / сортування. Щодо другого питання, я думаю, що існує принципова концептуальна різниця між унікальним стійким ідентифікатором, таким як ідентифікатор продукту, ніж тимчасовим списком продуктів. Для видалених продуктів повідомлення, наприклад, "Цей продукт не існує в системі", повідомляє вам щось конкретне про цей продукт.
Стів Віллок

1
Видалити всю інформацію про підкачки та сортування з маршруту добре. І підштовхувати його до параметрів POST - це погано. Здравствуйте? Питання про REST. Ми не використовуємо POST лише для скорочення URL-адреси в REST. Дієслово має сенс.
темто

1
Особисто я б не використовував параметри форми для запиту, оскільки це майже вимагає методу POST або PUT HTTP (оскільки в запиті є тіло). GET мені здається більш підходящим методом, оскільки і POST, і PUT передбачають зміну ресурсу. Через це я б пішов із додаванням більше параметрів запиту до URL, коли потрібно сортувати за кількома стовпцями.
Пол Д. Іден

1

Я схильний погодитися з slf, що "сторінка" насправді не є ресурсом. З іншого боку, варіант 3 є більш чистим, легшим для читання, і його можна легше здогадатися користувачем і навіть набрати, якщо потрібно. Я розірваний між варіантами 1 і 3, але не бачу причин не використовувати варіант 3.

Крім того, хоча вони виглядають приємно, один із недоліків використання прихованих параметрів, як згадував хтось, а не рядки запитів або сегменти URL-адреси, полягає в тому, що користувач не може розміщувати закладки або безпосередньо посилатися на певну сторінку. Це може бути, а може і не бути проблемою, залежно від програми, але лише щось, про що слід пам’ятати.


1
Що стосується вашої згадки про легше здогадатися, це не має значення. Якщо будувати API для гіпермедіа, користувачі ніколи не повинні вгадувати URI.
JR Garcia

0

Раніше я використовував рішення 3 (я пишу багато програм для джанго). І я не думаю, що в цьому немає нічого поганого. Це так само генеративно, як і два інших (якщо вам потрібно зробити якісь вискоблювання тощо), і це виглядає більш чисто. Крім того, ваші користувачі можуть здогадуватися за URL-адресами (якщо це програма для загального користування), а людям подобається, що вони можуть прямувати туди, куди хочуть, і вподобання за URL-адресами відчуває розширення можливостей.


0

Я використовую у своїх проектах такі URL-адреси:

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

що означає - "надіслати мені сторінку другою сторінкою, впорядкованою за зростанням по полю1, а потім низхідним по полю2". Або якщо мені потрібна ще більша гнучкість, я використовую:

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

0

Я використовую наступні зразки, щоб отримати наступну сторінку сторінки. http: // додаток / продукти? lastRecordKey =? & pageSize = 20 & sort = ASC

RecordKey - це стовпець таблиці, який містить послідовне значення в БД. Це використовується для отримання одночасно даних однієї сторінки з БД. pageSize використовується для визначення кількості записів для отримання. сортування використовується для сортування запису у порядку зростання чи спадання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.