Веб-разработка

REST API Design

В 2000 году Рой Филдинг защитил диссертацию, описывающую архитектурный стиль REST. Он работал над спецификацией HTTP и понял: если API будет следовать тем же принципам что и веб (ресурсы, идентификаторы, методы, representations), клиенты смогут работать с ним не зная деталей реализации. Сегодня REST - доминирующий стиль API. Но ирония в том, что большинство 'RESTful API' нарушают как минимум один из ключевых принципов Филдинга.

  • **Stripe API:** эталон REST дизайна - консистентное именование ресурсов, предсказуемые HTTP статусы, подробные сообщения об ошибках, cursor-based пагинация, idempotency keys для POST запросов
  • **GitHub API:** использует cursor пагинацию через заголовки Link (rel=next, rel=last), rate limiting через X-RateLimit-* заголовки, частичные ответы через ?fields=id,name
  • **OpenAPI/Swagger:** стандарт документирования REST API; автоматическая генерация клиентских библиотек из spec файла; 40 000+ публичных API используют OpenAPI

Ресурсо-ориентированный дизайн

Половина плохих REST API плохи по одной причине: разработчики думают глаголами, а не существительными. /getUser, /createOrder, /deleteProduct - это RPC, а не REST. REST строится вокруг ресурсов: /users, /orders, /products. Действие над ресурсом определяется HTTP-методом. Это кажется формальностью, но за этим стоит реальная польза: предсказуемость API, стандартный кешируемый GET, идемпотентные PUT/DELETE.

HTTP методы и их семантика: GET (читать, кешируемый, идемпотентный), POST (создать, не идемпотентный), PUT (заменить целиком, идемпотентный), PATCH (частичное обновление, условно идемпотентный), DELETE (удалить, идемпотентный). HTTP статусы: 200 (OK), 201 (Created), 204 (No Content), 400 (Bad Request), 401 (Unauthorized), 403 (Forbidden), 404 (Not Found), 409 (Conflict), 422 (Unprocessable Entity), 429 (Too Many Requests), 500 (Server Error).

Какой HTTP метод правильно использовать для отмены заказа (изменение статуса с 'confirmed' на 'cancelled')?

API Versioning

API версионирование - это не опциональная роскошь. Как только API публичный или используется мобильным приложением, нельзя ломать обратную совместимость. Пользователи со старой версией приложения должны продолжать работать. Стратегий несколько: версия в URL (/api/v1/), в заголовке (Accept: application/vnd.api+json;version=1), в subdomain (v1.api.example.com). Версия в URL - самая популярная, потому что видна в логах и легко тестируется.

Семантика версий API: major версия (v1, v2) - breaking changes (удаление полей, изменение типов), minor - additive changes (новые endpoints, опциональные поля). Правило: никогда не удалять поля в minor версии. Deprecation cycle: объявить deprecation, дать минимум 6-12 месяцев, мониторить использование через метрики, удалить. Sunset header: Sunset: Sat, 01 Jan 2026 00:00:00 GMT.

API v1 возвращает поле 'address' как строку. В v2 нужно вернуть его как объект { street, city, country }. Как правильно сделать миграцию?

Пагинация и фильтрация

API без пагинации - это временная бомба. Список из 50 записей работает прекрасно. Список из 50 000 - убивает базу данных и таймаутит клиента. Три основных подхода к пагинации имеют разные трейдофы: offset-based прост в реализации, но плохо работает при вставках; cursor-based стабилен при изменениях данных; keyset-based идеален по производительности. Выбор зависит от паттерна доступа.

Offset пагинация: LIMIT N OFFSET M - проста, поддерживает random access. Проблема: при вставке новой записи перед текущим offset страницы смещаются. Cursor пагинация: cursor = opaque token (base64 последнего ID или timestamp). Keyset пагинация: WHERE id > last_id ORDER BY id LIMIT N - использует индекс, не читает лишние строки. Стандарт ответа: items, total (для offset), nextCursor (для cursor), hasMore.

API возвращает страницы с offset пагинацией. Пока пользователь читает страницу 2, в базу добавляются новые записи в начало списка. Что увидит пользователь на странице 3?

HATEOAS и зрелость REST

HATEOAS (Hypermedia As The Engine Of Application State) - последний уровень модели зрелости REST по Ричардсону. Ответ API содержит не только данные, но и ссылки на возможные действия. Вместо того чтобы клиент знал URL /orders/{id}/cancel, сервер говорит: 'вот ваш заказ, вот что с ним можно сделать сейчас'. Это делает API самодокументируемым и позволяет серверу изменять URL без обновления клиентов.

Richardson Maturity Model: Level 0 (POX - Plain Old XML, один endpoint), Level 1 (Resources - отдельные URLs), Level 2 (HTTP Verbs - правильные методы), Level 3 (HATEOAS - hypermedia controls). Большинство 'REST API' на уровне 2. HAL (Hypertext Application Language) - популярный формат для HATEOAS: _links объект. JSON:API spec - более строгий стандарт включающий HATEOAS.

REST API с правильными HTTP методами и ресурсами автоматически является RESTful

Большинство так называемых REST API работают на уровне 2 модели Ричардсона; настоящий REST (Level 3 с HATEOAS) используется редко

Рой Филдинг (автор термина REST) неоднократно говорил, что то, что называют REST в индустрии, не является REST. HATEOAS - обязательная часть REST. Тем не менее, Level 2 API (ресурсы + HTTP методы) дают 90% пользы при минимальной сложности, и на практике это принятый компромисс.

Клиент использует HATEOAS API. Сервер изменил URL для отмены заказа с /orders/:id/cancel на /orders/:id (PATCH). Нужно ли обновлять клиент?

Ключевые идеи

  • **Ресурсо-ориентированный дизайн** - существительные в URL, глагол определяет HTTP метод; Location header при 201 Created
  • **Версионирование** - major версия в URL (/api/v2/); никогда не ломать существующих клиентов без deprecation цикла
  • **Cursor vs Offset пагинация** - cursor стабилен при изменениях данных; offset прост, но дает дубликаты при вставках в середину
  • **HATEOAS** - клиент читает URL из ответа, не хардкодит; позволяет серверу менять структуру без обновления клиентов

Связанные темы

REST API Design определяет контракт между клиентом и сервером:

  • Node.js и Express — Express предоставляет инструменты для реализации REST API - роутинг, middleware, error handling
  • GraphQL — GraphQL - альтернатива REST для сложных data-fetching требований; часто используется вместе с REST API

Вопросы для размышления

  • Stripe использует idempotency keys для POST запросов (клиент генерирует уникальный ключ для каждого запроса). Как это решает проблему дублирования при network timeout?
  • GraphQL vs REST: когда GraphQL выигрывает по developer experience и когда REST является лучшим выбором?
  • Как проектировать REST API для bulk операций (создать 1000 пользователей одним запросом)? Какие трейдофы у разных подходов?

Связанные уроки

  • net-21-http-basics
REST API Design

0

1

Войти