Я хотел бы получить некоторую помощь в обработке странного крайнего случая с разбитым на страницы 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 будут с благодарностью!
источник
Ответы:
Я не совсем уверен, как обрабатываются ваши данные, так что это может работать или не работать, но рассматривали ли вы разбиение на страницы с полем метки времени?
Когда вы запрашиваете / foos вы получаете 100 результатов. Ваш API должен затем возвращать что-то вроде этого (при условии JSON, но если ему нужен XML, могут следовать тем же принципам):
Только примечание, использование только одной временной отметки зависит от неявного «ограничения» в ваших результатах. Вы можете добавить явное ограничение или также использовать
until
свойство.Временная метка может быть определена динамически с использованием последнего элемента данных в списке. Похоже, это примерно так же, как Facebook разбивает на страницы в своем API API (прокрутите вниз до нижней части, чтобы увидеть ссылки на страницы в формате, который я дал выше).
Одной из проблем может быть, если вы добавляете элемент данных, но на основании вашего описания кажется, что они будут добавлены в конец (если нет, дайте мне знать, и я посмотрю, смогу ли я улучшить это).
источник
У вас есть несколько проблем.
Во-первых, у вас есть пример, который вы привели.
У вас также есть похожая проблема, если строки вставлены, но в этом случае пользователь получает дублирующиеся данные (возможно, легче управлять, чем отсутствующие данные, но все же проблема).
Если вы не делаете снимок исходного набора данных, это просто факт жизни.
Пользователь может сделать явный снимок:
Какие результаты:
Затем вы можете просматривать страницы в течение всего дня, поскольку теперь они статичны. Это может быть достаточно легким, поскольку вы можете просто захватывать фактические ключи документа, а не целые строки.
Если сценарий использования просто, что ваши пользователи хотят (и нуждаются) все данные, то вы можете просто дать им:
и просто отправить весь комплект.
источник
Если у вас есть нумерация страниц, вы также сортируете данные по некоторому ключу. Почему бы не позволить API-клиентам включить ключ последнего элемента ранее возвращенной коллекции в URL-адрес и добавить
WHERE
в ваш запрос SQL предложение (или что-то эквивалентное, если вы не используете SQL), чтобы он возвращал только те элементы, для которых ключ больше, чем это значение?источник
Там может быть два подхода в зависимости от вашей логики на стороне сервера.
Подход 1. Когда сервер недостаточно умен для обработки состояний объекта.
Вы можете отправить все кэшированные записи уникальных идентификаторов на сервер, например ["id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", «id10»] и логический параметр, чтобы узнать, запрашиваете ли вы новые записи (pull для обновления) или старые записи (загрузить больше).
Ваш сервер должен отвечать за возвращение новых записей (загружать больше записей или новых записей с помощью функции pull для обновления), а также идентификаторов удаленных записей из ["id1", "id2", "id3", "id4", "id5", " ID6" , "ИД7", "ID8", "ID9", "ID10"].
Пример: - Если вы запрашиваете нагрузку больше, ваш запрос должен выглядеть примерно так:
Теперь предположим, что вы запрашиваете старые записи (загрузите больше), и предположим, что запись «id2» кем-то обновлена, а записи «id5» и «id8» удалены с сервера, тогда ваш ответ сервера должен выглядеть примерно так: -
Но в этом случае, если у вас много локальных кэшированных записей, допустим 500, тогда строка вашего запроса будет слишком длинной, например:
Подход 2: Когда сервер достаточно умен, чтобы обрабатывать состояния объекта в соответствии с датой.
Вы можете отправить идентификатор первой записи, последней записи и времени начала предыдущего запроса. Таким образом, ваш запрос всегда мал, даже если у вас большое количество кэшированных записей.
Пример: - Если вы запрашиваете нагрузку больше, ваш запрос должен выглядеть примерно так:
Ваш сервер отвечает за возвращение идентификаторов удаленных записей, которые были удалены после last_request_time, а также за возврат обновленной записи после last_request_time между "id1" и "id10".
Потяните, чтобы обновить: -
Загрузи больше
источник
Это может быть сложно найти лучшие практики, так как большинство систем с API-интерфейсами не подходят для этого сценария, потому что это крайний край, или они обычно не удаляют записи (Facebook, Twitter). Facebook фактически говорит, что на каждой «странице» может отсутствовать количество запрошенных результатов из-за фильтрации, выполненной после нумерации страниц. https://developers.facebook.com/blog/post/478/
Если вам действительно нужно учесть этот крайний случай, вам нужно «вспомнить», где вы остановились. Предложение jandjorgensen почти точно, но я бы использовал поле, гарантированно уникальное, как первичный ключ. Вам может понадобиться использовать более одного поля.
Следуя потоку Facebook, вы можете (и должны) кэшировать уже запрошенные страницы и просто возвращать страницы с отфильтрованными удаленными строками, если они запрашивают уже запрошенную страницу.
источник
Пагинация - это, как правило, «пользовательская» операция, и для предотвращения перегрузки как на компьютерах, так и на человеческом мозге вы обычно задаете подмножество. Однако, вместо того, чтобы думать, что мы не получаем весь список, лучше спросить, имеет ли это значение?
Если требуется точное представление с прокруткой в реальном времени, API-интерфейсы REST, которые по своей природе являются запросом / ответом, не подходят для этой цели. Для этого вы должны рассмотреть WebSockets или HTML5 Server-Sent Events, чтобы ваш интерфейс знал об изменениях.
Теперь, если необходимо получить моментальный снимок данных, я бы просто предоставил вызов API, который предоставляет все данные в одном запросе без разбивки на страницы. Имейте в виду, вам нужно что-то, что будет выполнять потоковую передачу выходных данных без временной загрузки в память, если у вас большой набор данных.
В моем случае я неявно определяю некоторые вызовы API, чтобы получить всю информацию (в первую очередь данные справочной таблицы). Вы также можете защитить эти API, чтобы они не повредили вашу систему.
источник
Вариант A: разбиение на страницы набора ключей с отметкой времени
Во избежание упомянутых выше недостатков пагинации смещения вы можете использовать нумерацию на основе набора ключей. Обычно объекты имеют метку времени, которая указывает время их создания или изменения. Эта временная метка может использоваться для разбивки на страницы: просто передайте временную метку последнего элемента в качестве параметра запроса для следующего запроса. Сервер, в свою очередь, использует временную метку в качестве критерия фильтрации (например
WHERE modificationDate >= receivedTimestampParameter
)Таким образом, вы не пропустите ни одного элемента. Этот подход должен быть достаточно хорош для многих случаев использования. Однако имейте в виду следующее:
Вы можете уменьшить эти недостатки, увеличив размер страницы и используя временные метки с точностью до миллисекунды.
Вариант B: расширенная нумерация клавиш с помощью токена продолжения
Чтобы справиться с упомянутыми недостатками обычной нумерации клавиш, вы можете добавить смещение к отметке времени и использовать так называемый «токен продолжения» или «курсор». Смещение - это позиция элемента относительно первого элемента с такой же отметкой времени. Обычно токен имеет формат вроде
Timestamp_Offset
. Он передается клиенту в ответе и может быть отправлен обратно на сервер для получения следующей страницы.Маркер «1512757072_2» указывает на последний элемент страницы и утверждает, что «клиент уже получил второй элемент с отметкой времени 1512757072». Таким образом, сервер знает, где продолжить.
Пожалуйста, учтите, что вы должны обрабатывать случаи, когда элементы менялись между двумя запросами. Обычно это делается путем добавления контрольной суммы к токену. Эта контрольная сумма рассчитывается по идентификаторам всех элементов с этой отметкой времени. Таким образом , мы в конечном итоге с символической формой , как это:
Timestamp_Offset_Checksum
.Дополнительную информацию об этом подходе можно найти в блоге « Разбиение на страницы веб-API с токенами продолжения ». Недостатком этого подхода является сложная реализация, поскольку необходимо учитывать множество угловых случаев. Вот почему такие библиотеки, как extension-token, могут быть полезны (если вы используете язык Java / JVM). Отказ от ответственности: я автор поста и соавтор библиотеки.
источник
Я думаю, что в настоящее время ваши API действительно реагируют так, как должны. Первые 100 записей на странице в общем порядке объектов, которые вы ведете. Ваше объяснение говорит о том, что вы используете некие порядковые идентификаторы, чтобы определить порядок ваших объектов для нумерации страниц.
Теперь, если вы хотите, чтобы страница 2 всегда начиналась с 101 и заканчивалась на 200, вы должны сделать количество записей на странице переменным, поскольку они подлежат удалению.
Вы должны сделать что-то вроде следующего псевдокода:
источник
Просто чтобы добавить к этому ответу Камилка: https://www.stackoverflow.com/a/13905589
источник
Я долго думал над этим и, наконец, нашел решение, которое опишу ниже. Это довольно большой шаг по сложности, но если вы сделаете этот шаг, вы получите то, что вам действительно нужно, то есть детерминированные результаты для будущих запросов.
Ваш пример удаления элемента - это только верхушка айсберга. Что делать, если вы фильтруете,
color=blue
но кто-то меняет цвета элементов между запросами? Надежная выборка всех элементов постраничным способом невозможна ... если ... мы не внедрим историю изменений .Я реализовал это, и это на самом деле менее сложно, чем я ожидал. Вот что я сделал:
changelogs
с колонкой идентификатора с автоинкрементомid
поле, но это не первичный ключchangeId
поле, которое является как первичным ключом, так и внешним ключом для журналов изменений.changelogs
нее новую запись , захватывает идентификатор и назначает его новой версии сущности, которую затем вставляет в БД.changeId
представляет собой уникальный снимок базовых данных на момент создания изменения.changeId
навсегда. Результаты никогда не истекают, потому что они никогда не изменятся.источник
Другой вариант разбивки на страницы в API RESTFul - использовать представленный здесь заголовок Link . Например, Github использует его следующим образом:
Возможные значения
rel
: first, last, next, previous . Но с помощьюLink
заголовка может быть невозможно указать total_count (общее количество элементов).источник