Опирается на правила: R-QRY-1..9 и R-QRY-X1..X5 из REST API Style Guide → раздел Query-параметры и пагинация.

Важно знать

  • camelCase для имён query-параметров (customerId, dateFrom).
  • Фильтрация — имя поля как параметр (?status=CONFIRMED).
  • Диапазоны через суффиксы From/To (?dateFrom=...&dateTo=...).
  • Offset-basedpage (1-based!) + size. Для UI с номерами страниц.
  • Cursor-basedcursor (непрозрачный токен) + size. Для feed-style.
  • Сортировкаsort=поле,direction (sort=createdAt,desc).
  • Множественные значения — повтор параметра (?status=A&status=B).
  • Сложные запросыPOST /resources/search с JSON body.

Query — основной механизм фильтрации и пагинации. UCP формулирует чёткие правила: одна конвенция per use-case (offset vs cursor), параметры в camelCase, никаких comma-separated массивов или бизнес-команд в query.

Имена параметров

R-QRY-1: camelCase.

GET /orders?customerId=123&dateFrom=2026-01-01     ✓
GET /orders?customer_id=123                        ✗ — snake_case
GET /orders?CustomerID=123                         ✗ — PascalCase

Фильтрация и диапазоны

R-QRY-2..3:

GET /orders?status=CONFIRMED                       ✓ фильтр по полю
GET /orders?customerId=550e8400-...                ✓

GET /orders?dateFrom=2026-01-01&dateTo=2026-12-31  ✓ диапазон
GET /orders?amountFrom=100&amountTo=500            ✓

From/To[from, to] интервал inclusive. Это конвенция, не RFC, но distinct и понятна.

Offset-based пагинация

R-QRY-4: page (1-based!) + size.

GET /orders?page=1&size=20                         ← первая страница
GET /orders?page=3&size=50                         ← третья страница

Контракт API — 1-based. Первая страница = 1, не 0.

# application.yml
spring:
  data:
    web:
      pageable:
        one-indexed-parameters: true

Ручная конвертация page - 1 в коде запрещена — теряется единая точка истины.

Response:

{
  "content": [
    { "orderId": "...", "status": "CREATED" }
  ],
  "page": 1,
  "size": 20,
  "totalElements": 243,
  "totalPages": 13
}

Когда: произвольный переход на любую страницу, UI с номерами, нужно общее количество, данные относительно статичны.

Ограничения: при вставке/удалении между запросами элементы могут дублироваться или пропускаться; OFFSET в SQL деградирует на больших значениях.

Cursor-based пагинация

R-QRY-5: cursor (opaque) + size.

GET /orders?size=20                                ← первая страница
GET /orders?size=20&cursor=eyJpZCI6MTAwfQ==        ← следующая

Cursor — непрозрачный токен из предыдущего ответа. Клиент не парсит, не конструирует.

Response:

{
  "content": [...],
  "size": 20,
  "nextCursor": "eyJpZCI6MTIwfQ==",
  "prevCursor": "eyJpZCI6MTAwfQ==",
  "hasNext": true,
  "hasPrev": true
}

Когда: часто меняющиеся данные (feeds, messages, notifications); большие объёмы; infinite scroll; real-time потоки.

Ограничения: нет произвольного перехода на страницу N; нельзя узнать totalElements без отдельного запроса.

Server-side cursor — обычно Base64-encoded (lastId, lastCreatedAt). Клиенту не видны, чтобы можно было изменить схему без breaking change.

Сортировка

R-QRY-6: sort=field,direction.

GET /orders?sort=createdAt,desc
GET /orders?sort=totalAmount,asc&sort=createdAt,desc

Множественная сортировка через повтор sort. Допустима, но должна быть обоснована (риск проблем с составными индексами в БД — см. PG explain).

Полнотекстовый поиск

R-QRY-7: q.

GET /orders?q=term
GET /products?q=клавиатура

Один параметр для full-text. Сложные search-запросы — через POST /search.

Множественные значения

R-QRY-8: повтор параметра.

GET /orders?status=CREATED&status=CONFIRMED            ✓
GET /orders?status=CREATED,CONFIRMED                   ✗ — comma-separated

Стандарт OpenAPI:

parameters:
  - name: status
    in: query
    schema:
      type: array
      items:
        type: string
    style: form
    explode: true

style: form, explode: true — повтор. Это default behaviour Spring и большинства фреймворков.

R-QRY-X3: comma-separated ?status=A,B — запрещено:

  • Требует ручного парсинга на backend.
  • Ломается если значение содержит запятую.
  • Не default в OpenAPI.

POST /resources/search

R-QRY-9: для сложных запросов.

GET имеет практические ограничения:

  • Длина URL — 2000-8000 символов (зависит от proxy).
  • Невозможность передать вложенные структуры в query.

Когда поисковый запрос не укладывается:

POST /api/v1/orders/search
Content-Type: application/json

{
  "statuses": ["CONFIRMED", "PAID", "SHIPPED"],
  "dateRange": { "from": "2026-01-01", "to": "2026-12-31" },
  "customer": { "regionIds": [1, 5, 12], "segment": "VIP" },
  "totalAmount": { "from": 1000, "to": 50000 },
  "sort": [
    { "field": "createdAt", "direction": "DESC" },
    { "field": "totalAmount", "direction": "ASC" }
  ],
  "page": 1,
  "size": 20
}

Критерии перехода с GET на POST:

| Плоские поля (status, date) | GET достаточно | | Вложенные объекты | POST | | Массив 10+ значений в одном фильтре | POST | | Спецсимволы или свободный текст | POST | | AND/OR-комбинации | POST | | Запрос нужно сохранять/переиспользовать | POST |

Правила POST-поиска:

  1. URL: /resources/search (не /query, не /find).
  2. Метод: POST — GET с body формально не запрещён RFC, но игнорируется прокси и кешами.
  3. Код ответа: 200 OK (ресурс не создаётся).
  4. Формат ответа — тот же, что у GET /resources (пагинированный список).
  5. Идемпотентность: при кешировании — Idempotency-Key (см. Заголовки).

Что запрещено

АнтипаттернПравилоЧто взамен
?customer_id= snake_caseR-QRY-X1camelCase
page=0 (0-based в публичном контракте)R-QRY-X2page=1
?status=A,B comma-separatedR-QRY-X3?status=A&status=B
?action=cancel бизнес-логика в queryR-QRY-X4POST /orders/{id}/cancel
Клиент парсит cursorR-QRY-X5opaque token
?dateFrom=2026-01-01T00:00:00Z без dateToR-QRY-3оба или один (from only)
GET search с длинным URLR-QRY-9POST /search
Cursor с PII в payloadR-QRY-5Base64-encoded internal IDs

Куда дальше

  • REST API → Query-параметры (нормативно) — формулировки.
  • URL и ресурсы — формат пути.
  • JSON и формат ответов — пагинированный response.
  • Заголовки и трассировка — Idempotency-Key для POST search.
  • Batch, async, локализация — async-поиск.
  • PG explain — индексы под сортировку.