1. Основы REST API и принципы проектирования
Основы REST API и принципы проектирования
REST (Representational State Transfer) — это архитектурный стиль, описанный Роем Филдингом в его докторской диссертации 2000 года. Важно понимать: REST — не протокол и не стандарт, а набор ограничений (constraints), которым должна следовать система, чтобы называться RESTful. Нарушение хотя бы одного из них технически выводит API за рамки REST.
Шесть архитектурных ограничений REST
Клиент-серверная архитектура (Client-Server) разделяет ответственность: клиент управляет пользовательским интерфейсом и состоянием сессии, сервер — хранением данных и бизнес-логикой. Это позволяет масштабировать их независимо. Например, мобильное приложение и веб-интерфейс могут использовать один и тот же API, не зная ничего о внутреннем устройстве сервера.
Отсутствие состояния (Statelessness) означает, что каждый запрос от клиента должен содержать всю информацию, необходимую для его обработки. Сервер не хранит контекст между запросами. Практическое следствие: токен авторизации передаётся в каждом запросе, а не хранится в серверной сессии. Это упрощает горизонтальное масштабирование — любой узел кластера может обработать любой запрос.
Кэшируемость (Cacheability) требует, чтобы ответы явно помечались как кэшируемые или нет. Заголовки Cache-Control, ETag, Last-Modified — это прямое следствие данного ограничения. Правильная кэшируемость снижает нагрузку на сервер и уменьшает задержку для клиента.
Единообразный интерфейс (Uniform Interface) — ключевое ограничение, отличающее REST от других стилей. Оно включает четыре подпринципа: идентификация ресурсов через URI, манипуляция ресурсами через представления, самоописывающие сообщения и HATEOAS (Hypermedia as the Engine of Application State).
Многоуровневая система (Layered System) позволяет вставлять промежуточные слои — балансировщики нагрузки, кэши, шлюзы — между клиентом и сервером. Клиент не знает, с чем именно он общается напрямую.
Код по требованию (Code on Demand) — единственное необязательное ограничение. Сервер может передавать исполняемый код клиенту (например, JavaScript). На практике используется редко в контексте API.
Ресурсы как центральная концепция
В REST всё является ресурсом — сущностью, которую можно идентифицировать, именовать и представить. Ресурс — это не таблица в базе данных и не объект в коде, а концептуальная сущность предметной области.
Ключевое различие между ресурсом и его представлением (representation): ресурс User существует концептуально, а его представление — это JSON или XML, который возвращает сервер. Один ресурс может иметь несколько представлений, выбираемых через механизм content negotiation с помощью заголовка Accept.
Проектирование URI: правила и типичные ошибки
URI (Uniform Resource Identifier) должен идентифицировать ресурс, а не действие. Это принципиальное отличие REST от RPC-стиля.
| Плохо (RPC-стиль) | Хорошо (REST-стиль) |
|---|---|
| POST /getUser | GET /users/42 |
| POST /createOrder | POST /orders |
| GET /deleteProduct?id=5 | DELETE /products/5 |
| POST /updateUserStatus | PATCH /users/42 |
| GET /getUserOrders?userId=42 | GET /users/42/orders |
Правила именования URI:
/users, /orders, /products/users/42/orders/7 — заказ №7 пользователя №42/product-categories, не /product_categories/users/42, не /users/42.jsonТипичная ошибка на собеседовании — смешивать уровни вложенности. Если заказ может существовать независимо от пользователя, лучше использовать /orders/7, а не всегда требовать /users/42/orders/7. Глубокая вложенность (более 2–3 уровней) усложняет API и часто указывает на проблемы в дизайне.
HTTP-методы и их семантика
Каждый HTTP-метод несёт строгую семантику, которую нельзя нарушать произвольно.
GET — получение ресурса. Безопасный (safe) и идемпотентный. Не должен изменять состояние сервера. Может кэшироваться.
POST — создание нового ресурса или выполнение действия. Не идемпотентный: два одинаковых POST-запроса создадут два ресурса. Ответ на успешное создание — 201 Created с заголовком Location, указывающим на новый ресурс.
PUT — полная замена ресурса. Идемпотентный: повторный PUT с теми же данными даёт тот же результат. Клиент передаёт полное представление ресурса. Если поле не передано — оно обнуляется.
PATCH — частичное обновление. Клиент передаёт только изменяемые поля. Технически не обязан быть идемпотентным (хотя на практике часто является).
DELETE — удаление ресурса. Идемпотентный: повторное удаление уже удалённого ресурса возвращает 404, но состояние системы не меняется.
Коды HTTP-ответов: точность имеет значение
Правильное использование кодов ответа — признак зрелого API. Распространённая ошибка — возвращать 200 OK с телом {"error": "not found"}. Это ломает все инструменты мониторинга и клиентские библиотеки.
| Диапазон | Смысл | Примеры | |---|---|---| | 2xx | Успех | 200 OK, 201 Created, 204 No Content | | 3xx | Перенаправление | 301 Moved Permanently, 304 Not Modified | | 4xx | Ошибка клиента | 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity | | 5xx | Ошибка сервера | 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable |
Тонкие различия, которые проверяют на собеседованиях:
401 Unauthorized — клиент не аутентифицирован (несмотря на название, это про authentication)403 Forbidden — клиент аутентифицирован, но не имеет прав (authorization)404 Not Found — ресурс не существует, или сервер намеренно скрывает его существование409 Conflict — конфликт состояния (например, попытка создать пользователя с уже существующим email)422 Unprocessable Entity — запрос синтаксически корректен, но семантически невалиден (например, дата окончания раньше даты начала)Принципы проектирования: что отличает хороший API
Консистентность — самое важное свойство. Если в одном эндпоинте поле называется userId, в другом оно не должно называться user_id или uid. Если пагинация реализована через page/per_page в одном месте, она должна быть такой же везде.
Принцип наименьшего удивления (Principle of Least Astonishment): поведение API должно соответствовать ожиданиям разработчика. Если DELETE /users/42 возвращает тело с данными удалённого пользователя — это удивит большинство клиентов. Стандартное поведение — 204 No Content.
Явность над неявностью: лучше требовать явного указания параметров, чем полагаться на дефолтные значения, которые клиент может не знать. Например, если API по умолчанию возвращает только активных пользователей, это должно быть задокументировано и, желательно, управляться явным параметром ?status=active.
Обратная совместимость (backward compatibility): изменения в API не должны ломать существующих клиентов. Добавление нового поля в ответ — безопасно. Удаление поля или изменение его типа — breaking change, требующий версионирования.
> Хороший API — это продукт. Он должен быть спроектирован с точки зрения разработчика, который будет его использовать, а не с точки зрения разработчика, который его создаёт.
Реальный пример плохого дизайна: API возвращает список заказов, но статус заказа кодируется числом (1 — новый, 2 — в обработке, 3 — доставлен). Клиент вынужден хранить маппинг. Лучше возвращать строку "status": "processing" — это самодокументируемо и устойчиво к добавлению новых статусов.
!Архитектура REST API: клиент, сервер, ресурсы и HTTP-методы
HATEOAS на практике
HATEOAS — самое часто игнорируемое ограничение REST. Идея: ответ сервера должен содержать ссылки на связанные действия, чтобы клиент мог навигировать по API, не зная его структуры заранее.
На практике полный HATEOAS редко реализуется из-за сложности поддержки. Но частичная реализация — включение ссылок на связанные ресурсы — встречается в зрелых API (GitHub API, Stripe API). На собеседовании важно знать концепцию и уметь объяснить, почему большинство "REST API" на самом деле не являются полностью RESTful.