|
Professor Seleznov
|
Привет всем, на связи снова Дарья Борисова, системный аналитик из ПСБ. Продолжаю развеивать мифы о REST API. Если вы пропустили первую и вторую часть, то советую заглянуть туда: ведь мы уже разобрали некоторые заблуждения о природе REST. Сегодня мы разберем нюансы транспортных и бизнес-ошибок, погрузимся в кеширование и узнаем, действительно ли REST должен быть прокси для базы данных. Переходите под кат, начинаем! Миф 1. HTTP-коды только для транспортных ошибок, бизнес-ошибки всегда 200.
 Миф HTTP-статусы (4xx, 5xx) — это только технические детали протокола. Они описывают исключительно процесс обмена данными между клиентом и сервером. Например: 404 Not Found — запрошенный URL не существует 403 Forbidden — сервер понял запрос, но не разрешает доступ 500 Internal Server Error — сбой на стороне сервера Из этого появляется вывод: Все, что связано с бизнес-логикой (например, найден ли заказ, можно ли его отменить), должно передаваться только в теле ответа, а HTTP-статус почти всегда должен быть 200 OK (или иногда 400 Bad Request). Реальность В REST-подходе HTTP-статусы — это основной способ сообщить результат операции. Они не второстепенны — они часть самого интерфейса. Объяснение 1. REST — это не просто JSON по HTTP. REST — это архитектурный стиль с четкими правилами. Одно из ключевых — единый интерфейс. HTTP уже реализует этот интерфейс, и статусы — его важная часть. Они:
- стандартизированы;
- понятны программам;
- работают «из коробки» для прокси, кэшей, API-шлюзов.
2. Деление на «технические» и «бизнес-» ошибки — искусственное Для клиента ощутимой разницы нет:
- «страница не найдена»;
- «заказ не найден».
В обоих случаях результат один: запрошенный ресурс отсутствует. И код 404 идеально это передает — без дополнительных соглашений и костылей. 3. Что это значит для дизайна API Вот как обычно правильно использовать статусы:
- Если /api/orders/999 не существует — это 404 Not Found. Можно добавить JSON для вывода пользователю, но необязательно.
- Пользователь авторизован, но не имеет прав — это 403 Forbidden. Четко разделяет:
кто ты? — 401;
можно ли тебе это? — 403.
- Конфликт с текущим состоянием ресурса — 409 Conflict. Например: заказ уже выполнен, email уже занят.
- Ошибка в бизнес-правилах — 422 Unprocessable Entity. Запрос корректный по форме, но выполнить его нельзя. Например: дата окончания раньше даты начала.
4. Почему 200 OK для ошибок — плохая идея Формат вроде: { "success": false, "error": "..." } Это антипаттерн. Всё превращается в «что-то пошло не так» Почему это плохо:
- Ломает инфраструктуру.
Прокси, мониторинг и логирование не понимают, что произошла ошибка.
- Усложняет логику на клиенте.Клиенту приходится проверять HTTP-статус и поле в JSON.
- Стирает различия между ситуациями.Нет разницы между: не найдено (404), нет прав (403), ошибка запроса (400).
ВыводПри проектировании RESTful API рассматривайте HTTP-статусы как первичный канал передачи состояния операции в рамках домена. Ваша задача — сопоставить бизнес-сценарии («пользователь не найден», «заказ уже оплачен», «лимит исчерпан») с наиболее подходящими, семантически богатыми кодами состояния из стандарта HTTP. Это не «загрязнение» протокола бизнес-логикой, а, наоборот, правильное использование предоставленного стандартом «словаря» для создания понятного и эффективного интерфейса. Миф 2. REST API должен быть тонким слоем над БД.
 Миф REST API должен быть тонким слоем над базой данных: один HTTP-запрос — одна операция CRUD над таблицей. И если у нас только CRUD-операции, то REST API неизбежно становится тонким слоем над базой данных. Реальность REST — это про работу с ресурсами через интерфейс, а не требование отражать структуру базы данных. API может и часто должен содержать полноценную бизнес-логику, но при этом CRUD-операция не должна раскрывать структуру хранения Объяснение Этот миф возникает из-за упрощённого понимания REST как «обёртки над CRUD». Действительно, базовые операции (создание, чтение, обновление, удаление) удобно маппятся на HTTP-методы (POST, GET, PUT/PATCH, DELETE). Однако это лишь частный случай, а не цель архитектуры. Как понять, что ваши методы — это «прокси для БД»?
- Клиенту за один запрос отдаются все столбцы одной таблицы, в том числе технические поля. Нет агрегации данных из нескольких таблиц/систем.
- Вся бизнес-логика либо не прописана либо вынесена на клиента.
- API жёстко повторяет структуру базы данных, и любое изменение схемы БД ломает внешний контракт.
- Невозможны разные представления одного ресурса (например, краткое и полное).
Чем грозит «тонкий слой»:
- утечка внутренней структуры данных наружу;
- дублирование логики на клиентах;
- сложности с версионированием;
- рост технического долга при изменении предметной области.
Как исправить?
- Работать с ресурсами, а не таблицами.
Один ресурс ≠ одна таблица. Т.е. клиент должен получать нужные ему данные, а не те, которые удобны серверу.
- Встраивать бизнес-логику в обработку метода.
Неважно, будет она в коде сервиса или в самой БД в качестве процедуры, главное — чтобы на стороне сервера.
- Проверять инварианты.
То есть, соблюдение условия, что действие над ресурсом является разрешенным и легитимным.
- Не бояться выходить за рамки обычного CRUD.
Иногда честнее сделать:
POST /заказы/{id}/подтверждение POST /заказы/{id}/отмена чем: PATCH с полем «статус»: «подтверждён» Вывод REST API — это не отражение базы данных, а контракт уровня предметной области. Чем дальше API от структуры хранения и ближе к бизнес-смыслу ресурсов и операций, тем он устойчивее, безопаснее и проще в развитии. Миф 3. Кеширование в REST — это про заголовки HTTP и всё.
 Миф На практике многие команды считают, что они «поддерживают кеширование», если добавили Cache-Control и ETag.И на этом всё заканчивается. Реальность На деле мы имеем большую градацию способов. Рассмотрим подробно. Объяснение Для client-server Цель кеширования:
- Снижение связности (клиент может вообще не ходить на сервер за данными);
- Ответственность за данные переносится за рамки сервиса. Сервер перестаёт быть единственной точкой истины в момент запроса.Появляется «согласованность в итоге» на уровне HTTP;
- Снижение задержек при ответе клиенту.
В чем ошибка большинства:
- все ответы помечаются как no-store;
- все ответы одинаково кешируются.
Не учитывается, что все ресурсы имеют разную природу, скорость и модель изменения. Что делать для вызовов типа client-server?
- Не игнорировать If-None-Match и If-Modified-Since. Они помогут уменьшить нагрузку без потери актуальности
- (самое сложное) Определить стратегию инвалидации кеша (когда, кто, как синхронизироваться?)
А что происходит в вызовах типа server-server? Обычно команды боятся: «А вдруг пользователь увидит неактуальные данные?» И выбирают Cache-Control: no-store. В итоге те самые слабосвязанные микросервисы теряют это волшебное свойство слабой связности. А еще встает вопрос кто отвечает за инвалидирование кеша? Ответ обычно: никто. И если все-таки ответственного за инвалидацию кеша нашли, Cache-Control: no-store исправили, то встает новый вопрос: «Зачем нам кеш, если вызовы идут по внутренней сети с минимальной задержкой? Для нас кеш — источник багов, а не оптимизации». Но это не так. Микросервисы, несмотря требования, что данные обновляются мгновенно и всегда остаются актуальными, усиливают потребность в кеше, позволяя системе масштабироваться с меньшими затратами. Как сделать «зрелый» кеш для server-server?
- Проектировать кешируемость на уровне контракта: определение TTL и различие типов ресурсов;
- Принять тот факт, что данные не могут быть всегда актуальными и определить степень актуальности, ввести SLA на свежесть. Проработать измеримость данного признака;
- Ввести многослойный кеш (это уже не только на уровне REST: на гейтвее, на уровне сервиса);
- Комбинировать с событиями, т. е. события инвалидируют кеш или обновляют модель чтения.
Вывод Кеширование в REST — это не про Cache-Control, а про готовность системы жить с устаревшими данными ради снижения связности. Не отказывайтесь от кеша только потому, что вам лень управлять консистентностью. ____ На этом я завершаю цикл статей о мифах REST. Надеюсь, вам было интересно. Делитесь в комментариях впечатлениями.-Источник
|