Изображение от Freepik

В вашем сервисе есть API для запроса массива объектов с фильтрацией и пагинацией. Но что делать, если эти объекты переезжают в другой источник данных? Мигрировать существующие данные в новое место и перенастроить конфигурацию вашего сервиса. А если такая миграция недоступна? Найдётся вариант и на этот случай!

Предположим, что у нас есть ничем не примечательный микросервис или монолит, который среди прочего предоставляет API для получения списка объектов. Для выдачи списка применяется пагинация, основанная на offset/limit. В этом случае запрос списка данных содержит указание порядкового номера первой запрашиваемой записи или страницы (offset) и размер одной страницы (limit). В ответе, в дополнении к запрошенной странице данных, содержится информация об общем количестве элементов списка и общем количестве страниц. При использовании реляционных БД подобного можно достичь, используя отдельный запрос количества записей, попадающих в выборку, и запрос самих записей, попадающих на требуемую страницу. Такое API обычно поддерживает фильтрацию и сортировку списка и может использоваться как для отображения в интерфейсе пользователя, так и для других целей.

API получения списка объектов

Спринты идут и появляются новые требования. Планируются изменения в наших объектах. Появляется новое место хранения данных и способ их получения. Например, переход на использование другой СУБД или несовместимые изменения схемы данных в текущей БД. При этом предусматривается переходный период, когда в системе должны функционировать обе версии объектов. По факту, этот переходный период может растянуться на месяца. Встаёт вопрос, как не сломать наше API запроса списка объектов, с учётом, того, что их необходимо получать из двух источников?

Запрос всех данных из двух источников с последующей агрегацией и фильтрацией силами рассматриваемого сервиса можно отбросить сразу, в силу неэффективности. Предлагаю рассмотреть подход, разделяющий все страницы объектов на две группы по признаку их источника. При этом все объекты из нового источника данных будут расположены в первых i страницах выдачи. Все объекты из старого источника данных будут расположены на страницах, начиная с i+1. Такое разделение на две группы происходит с учётом указанных в запросе фильтров.

Совместная пагинация старых и новых объектов

Алгоритм состоит из следующих шагов:

  1. В исходном запросе присутствуют: запрашиваемая страница requested_page, желаемый размер страницы page_size и опциональные параметры фильтрации.

  2. Получение общего количества старых объектов total_items_old, попадающих в выборку.

  3. Вычисление количества страниц старых объектов
    total_pages_old = ceil(total_items_old/page_size).

  4. Получение общего количества новых объектов total_items_new, попадающих в выборку.

  5. Вычисление количества страниц новых объектов
    total_pages_new = ceil(total_items_new/page_size).

  6. Если запрашиваемая страница requested_page попадает в диапазон
    [1, total_pages_new], то делаем запрос данных из нового места хранения объектов.

  7. Иначе, делаем запрос в старое место хранения, не забыв преобразовать номер нужной страницы actual_page = requested_page - total_pages_new.

  8. В ответе в качестве общего количества страниц и объектов следует отдать (total_pages_old + total_pages_new) и (total_items_old + total_items_new) соответственно.

  9. В результате запроса нужно отдать список объектов, полученный в п.6 или п.7.

Схема алгоритма

Описанный подход содержит несколько компромиссов. Серьёзный минус - это невозможность использовать сортировку агрегированного списка объектов. Менее значительный - наличие страницы с номером total_pages_new, которая в большинстве случаев будет содержать меньше записей, чем запрошено. Если такое поведение неприемлемо, то можно усложнить расчёт отступов, чтобы заполнить эту страницу объектами из старого источника данных.

Спасибо за внимание, пусть ваши временные решения не становятся постоянными.