Привет всем, на связи снова Дарья Борисова, системный аналитик из ПСБ. Продолжаю развеивать мифы о 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. Надеюсь, вам было интересно. Делитесь в комментариях впечатлениями.
