Логотип Workflow

Article

Updated at:

Stage 14: Pagination, Sorting, and Filtering

Этап 14 - Pagination, sorting и filtering

Backend code становится тяжелым для поддержки, когда важное поведение спрятано в случайных controller methods. Эта статья про Pageable, sorting и filtering. Тема выглядит технической, но настоящий вопрос практический: что произойдет, когда реальный client отправит реальный request, а в нем что-то неполное, слишком большое, запрещенное, медленное или конфликтующее с текущим состоянием?

Вернуть все строки работает на десяти records и ломается на десяти миллионах. Backend list endpoint требует page size limits, stable sorting и explicit filters, которые совпадают с indexed database access patterns.

Этап 14 - Pagination, sorting и filtering

Картинка, которую нужно держать в голове

Представь маленький online shop. User открывает frontend, нажимает кнопку, и browser вызывает backend. Backend принимает HTTP data, преобразует их в Java objects, проверяет rules, трогает database и возвращает JSON. Если у каждого шага есть понятный владелец, систему можно читать. Если все шаги смешаны в одном методе, первый production incident становится болезненным.

Для этой темы последовательность такая:

  1. Query params.
  2. validated filter.
  3. Pageable.
  4. repository query.
ЧастьОтветственность
ControllerПринимает HTTP data и возвращает публичный response.
ServiceВыполняет business use case и защищает business rules.
Repository/adapterРаботает с persistence или external systems.
DTO/contractОписывает, что внешний мир может отправить или получить.

Конкретный Spring пример

@GetMapping("/api/orders")
Page<OrderResponse> list(
    @RequestParam(required = false) OrderStatus status,
    @PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
    return orderService.list(status, pageable);
}

Код специально маленький, потому что главное здесь - граница ответственности. Controller не должен незаметно решать business rules, которые принадлежат service. Service не должен зависеть от servlet objects. Repository не должен знать, какое JSON field name ожидает frontend. Когда граница чистая, то же поведение можно документировать, тестировать и менять без правок во всех слоях.

Почему это важно

В demo project многие shortcuts выглядят безобидно. Вернуть все records нормально, пока в таблице пять строк. Вернуть entity нормально, пока в ней не появилось скрытое поле. Логировать без request id нормально, пока несколько users не падают одновременно. Работать с вручную созданной database нормально, пока staging не получает другую schema. Production work в основном состоит в том, чтобы убрать такие hidden assumptions до того, как они станут incidents.

Самое простое полезное правило: сделай behavior явным на границе, enforce его в правильном слое и держи public contract стабильным. Если frontend знает request format, response format и error behavior, он работает увереннее. Если tests проверяют тот же contract, refactoring становится безопаснее. Если logs и configuration отражают тот же design, operations меньше зависят от угадывания.

Частые ошибки

  • Помещать весь use case в controller method.
  • Позволять persistence details протекать в API contract.
  • Обрабатывать happy path, но оставлять failure behavior неопределенным.
  • Использовать local shortcut, который не сможет работать в staging или production.

Чеклист понимания

  • Я могу нарисовать последовательность этой темы от request до response.
  • Я могу объяснить, какой слой владеет решением.
  • Я могу назвать production problem, которую эта тема предотвращает.

Вопросы для самопроверки

  1. Что произойдет, если каждый endpoint реализует это правило по-своему?
  2. Какая часть behavior должна быть задокументирована для API clients?
  3. Какой test докажет, что правило работает, а не только happy path?