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

Это антипаттерн. Всё превращается в «что-то пошло не так»

Почему это плохо:

  1. Ломает инфраструктуру.
    Прокси, мониторинг и логирование не понимают, что произошла ошибка.

  2. Усложняет логику на клиенте.
    Клиенту приходится проверять HTTP-статус и поле в JSON.

  3. Стирает различия между ситуациями.
    Нет разницы между: не найдено (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). Однако это лишь частный случай, а не цель архитектуры.

Как понять, что ваши методы — это «прокси для БД»?

  1. Клиенту за один запрос отдаются все столбцы одной таблицы, в том числе технические поля. Нет агрегации данных из нескольких таблиц/систем.

  2. Вся бизнес-логика либо не прописана либо вынесена на клиента.

  3. API жёстко повторяет структуру базы данных, и любое изменение схемы БД ломает внешний контракт.

  4. Невозможны разные представления одного ресурса (например, краткое и полное).

Чем грозит «тонкий слой»:

  • утечка внутренней структуры данных наружу;

  • дублирование логики на клиентах;

  • сложности с версионированием;

  • рост технического долга при изменении предметной области.

Как исправить?

  1. Работать с ресурсами, а не таблицами.
    Один ресурс ≠ одна таблица. Т.е. клиент должен получать нужные ему данные, а не те, которые удобны серверу.

  2. Встраивать бизнес-логику в обработку метода.
    Неважно, будет она в коде сервиса или в самой БД в качестве процедуры, главное — чтобы на стороне сервера.

  3. Проверять инварианты.
    То есть, соблюдение условия, что действие над ресурсом является разрешенным и легитимным.

  4. Не бояться выходить за рамки обычного CRUD.
    Иногда честнее сделать:

POST /заказы/{id}/подтверждение

POST /заказы/{id}/отмена

чем:

PATCH с полем «статус»: «подтверждён»

Вывод

REST API — это не отражение базы данных, а контракт уровня предметной области. Чем дальше API от структуры хранения и ближе к бизнес-смыслу ресурсов и операций, тем он устойчивее, безопаснее и проще в развитии.

Миф 3. Кеширование в REST — это про заголовки HTTP и всё.

Миф

На практике многие команды считают, что они «поддерживают кеширование», если добавили Cache-Control и ETag.И на этом всё заканчивается.

Реальность

На деле мы имеем большую градацию способов. Рассмотрим подробно.

Объяснение

Для client-server

Цель кеширования:

  1. Снижение связности (клиент может вообще не ходить на сервер за данными);

  2. Ответственность за данные переносится за рамки сервиса. Сервер перестаёт быть единственной точкой истины в момент запроса. Появляется «согласованность в итоге» на уровне HTTP;

  3. Снижение задержек при ответе клиенту.

В чем ошибка большинства:

  • все ответы помечаются как no-store;

  • все ответы одинаково кешируются. 

Не учитывается, что все ресурсы имеют разную природу, скорость и модель изменения.

Что делать для вызовов типа client-server?

  1. Не игнорировать If-None-Match и If-Modified-Since. Они помогут уменьшить нагрузку без потери актуальности

  2. (самое сложное) Определить стратегию инвалидации кеша (когда, кто, как синхронизироваться?)

А что происходит в вызовах типа server-server?

Обычно команды боятся: «А вдруг пользователь увидит неактуальные данные?» И выбирают Cache-Control: no-store. В итоге те самые слабосвязанные микросервисы теряют это волшебное свойство слабой связности. А еще встает вопрос кто отвечает за инвалидирование кеша? Ответ обычно: никто.

И если все-таки ответственного за инвалидацию кеша нашли, Cache-Control: no-store исправили, то встает новый вопрос:

«Зачем нам кеш, если вызовы идут по внутренней сети с минимальной задержкой? Для нас кеш — источник багов, а не оптимизации». 

Но это не так. Микросервисы, несмотря требования, что данные обновляются мгновенно и всегда остаются актуальными, усиливают потребность в кеше, позволяя системе масштабироваться с меньшими затратами. 

Как сделать «зрелый» кеш для server-server?

  1. Проектировать кешируемость на уровне контракта: определение TTL и различие типов ресурсов;

  2. Принять тот факт, что данные не могут быть всегда актуальными и определить степень актуальности, ввести SLA на свежесть. Проработать измеримость данного признака;

  3. Ввести многослойный кеш (это уже не только на уровне REST: на гейтвее, на уровне сервиса);

  4. Комбинировать с событиями, т. е. события инвалидируют кеш или обновляют модель чтения.

Вывод

Кеширование в REST — это не про Cache-Control, а про готовность системы жить с устаревшими данными ради снижения связности. Не отказывайтесь от кеша только потому, что вам лень управлять консистентностью.

____

На этом я завершаю цикл статей о мифах REST. Надеюсь, вам было интересно. Делитесь в комментариях впечатлениями.