9. Query-параметры
9.1 Обязательно
-
R-QRY-1. Имена параметров — camelCase.
GET /api/v1/orders?customerId=123&dateFrom=2025-01-01 -- правильно -
R-QRY-2. Фильтрация по полю — имя поля как параметр.
GET /api/v1/orders?status=CONFIRMED GET /api/v1/orders?customerId=550e8400-e29b-41d4-a716-446655440000 -
R-QRY-3. Диапазоны — суффиксы
From/To.GET /api/v1/orders?dateFrom=2025-01-01&dateTo=2025-12-31 GET /api/v1/orders?amountFrom=100&amountTo=500 -
R-QRY-4. Offset-based пагинация: параметры
page(1-based) иsize.GET /api/v1/orders?page=1&size=20 GET /api/v1/orders?page=3&size=50page— номер страницы, 1-based: первая страница = 1.size— количество элементов на странице. Значение по умолчанию задаётся сервером (например, 20).
Реализация: контракт API — 1-based; в Spring Data конфигурируется
spring.data.web.pageable.one-indexed-parameters=true. Ручная конвертацияpage-1в коде запрещена — теряется единая точка истины.Пример ответа:
{ "content": [ { "orderId": "...", "status": "CREATED" } ], "page": 1, "size": 20, "totalElements": 243, "totalPages": 13 }Когда использовать: произвольный переход на любую страницу; нужно знать общее количество (
totalElements); данные относительно статичны; UI с номерами страниц.Ограничения: при вставке/удалении между запросами элементы могут дублироваться или пропускаться;
OFFSETв SQL деградирует на больших смещениях. -
R-QRY-5. Cursor-based пагинация: параметры
cursor(непрозрачный токен) иsize.GET /api/v1/orders?size=20 -- первая страница GET /api/v1/orders?size=20&cursor=eyJpZCI6MTAwfQ== -- следующая страницаcursor— непрозрачный токен из предыдущего ответа. Клиент НЕ парсит и НЕ конструирует его.size— количество элементов.
Пример ответа:
{ "content": [ { "orderId": "...", "status": "CREATED" } ], "size": 20, "nextCursor": "eyJpZCI6MTIwfQ==", "prevCursor": "eyJpZCI6MTAwfQ==", "hasNext": true, "hasPrev": true }Когда использовать: часто меняющиеся данные (ленты, сообщения, уведомления); большие объёмы; infinite scroll; real-time потоки.
Ограничения: нет произвольного перехода на страницу N; нельзя узнать общее количество без отдельного запроса.
-
R-QRY-6. Сортировка — параметр
sort, форматимяПоля,направление.GET /api/v1/orders?sort=createdAt,desc GET /api/v1/orders?sort=totalAmount,asc&sort=createdAt,descМножественная сортировка через повтор параметра допустима, но должна быть обоснована (риск проблем с составными индексами).
-
R-QRY-7. Полнотекстовый поиск — параметр
q.GET /api/v1/orders?q=термин GET /api/v1/products?q=клавиатура -
R-QRY-8. Множественные значения — повтор параметра.
GET /api/v1/orders?status=CREATED&status=CONFIRMED -- правильноЭто стандартный способ передачи массивов в query string. Поддерживается из коробки большинством фреймворков и корректно описывается в OpenAPI через
style: form, explode: true. -
R-QRY-9. Сложные поисковые запросы —
POST /resources/searchс телом JSON.GET имеет практические ограничения: длина URL (~2000–8000 символов), невозможность передать вложенные структуры в query. Когда поисковый запрос не укладывается —
POST /resources/search:POST /api/v1/orders/search Content-Type: application/json { "statuses": ["CONFIRMED", "PAID", "SHIPPED"], "dateRange": { "from": "2025-01-01", "to": "2025-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 (GET ломает читаемость из-за кодирования)
- AND/OR-комбинации → POST
- Запрос нужно сохранять/переиспользовать → POST
Правила POST-поиска:
- URL:
/resources/search(не/query, не/find, не/search/resources). - Метод:
POST— GET с телом формально не запрещён RFC, но на практике тело игнорируется прокси, кешами и многими клиентами. - Код ответа:
200 OK(ресурс не создаётся). - Формат ответа — тот же, что у
GET /resources(пагинированный список). - Идемпотентность: POST-поиск идемпотентен по факту, но HTTP этого не гарантирует — при необходимости кеширования добавь заголовок
Idempotency-Key(см.R-HDR-3в Заголовки).
9.2 Запрещено
- R-QRY-X1. snake_case или PascalCase в именах параметров.
?customer_id=123 -- неправильно ?CustomerID=123 -- неправильно - R-QRY-X2.
page=0или 0-based нумерация в публичном контракте. - R-QRY-X3. Comma-separated значения для массивов.
?status=CREATED,CONFIRMED -- неправильноПричины: требует ручного парсинга на бэкенде; ломается, если значение содержит запятую; не соответствует поведению по умолчанию OpenAPI (
explode: trueвstyle: form). - R-QRY-X4. Бизнес-логика в query-параметре вместо action-эндпоинта.
/orders?action=cancel -- неправильно POST /orders/{id}/cancel -- правильно - R-QRY-X5. Парсинг или конструирование
cursorна стороне клиента.