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

Комментарии (42)


  1. zo0Mx
    05.05.2026 06:53

    я не знаю откуда эти "мифы" взяты и кто их считает таковыми, впервые слышу


    1. whoisking
      05.05.2026 06:53

      Я встречал немало любителей заворачивать всё в 200 ОК и придумывать кастомные ошибки для всего


      1. Gromilo
        05.05.2026 06:53

        Никто не запрещает использовать http только как транспорт, например https://en.wikipedia.org/wiki/JSON-RPC

        Но это не REST.

        Правда REST почти не бывает, ибо в апи почти ни у кого нет гипермедия


  1. Rsa97
    05.05.2026 06:53

    Если /api/orders/999 не существует — это 404 Not Found. Можно добавить JSON для вывода пользователю, но необязательно.

    А если /api/book/350/page/12 вернул 404, то чего именно не существует - книги с id 350, страницы 12 в этой книге или вообще api? А если 403, то на что именно у клиента нет разрешения - на доступ к книге вообще, к конкретной странице или к операции (PUT/PATH/DELETE)?

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

    Но, чтобы понять, что именно произошло, клиенту и так придётся откуда-то эту информацию брать.

    Одно из ключевых — единый интерфейс. HTTP уже реализует этот интерфейс, и статусы — его важная часть.

    А что делать, если мы хотим использовать REST одновременно и по HTTP и по WebSocket? У последнего никаких статусов нет. Что в таком случае означает “единый интерфеейс”?


    1. whoisking
      05.05.2026 06:53

      А если /api/book/350/page/12 вернул 404, то чего именно не существует - книги с id 350, страницы 12 в этой книге или вообще api?

      А какая разница? У вас хождение в глубину, чтобы юзеру попасть на page 12 надо сперва попасть на book 350. Для SEO вы будете генерировать sitemap и это ваша задача держать его обновлённым, там в целом тоже без разницы почему ссылка не валидна. Для кого это необходимо, кто целевая аудитория этого функционала?


      1. Rsa97
        05.05.2026 06:53

        Да читатель этой библиотеки. Что ему сказать при такой ссылке - “нет книги” или “нет страницы”?


        1. ws233
          05.05.2026 06:53

          Нет страницы? Даже если нет книги, то страницы-то тоже нет. В чем противоречие?


          1. Rsa97
            05.05.2026 06:53

            В точности описания ошибки. Если вам вернулось “нет такой страницы” вы, скорее всего, будете проверять, а есть ли другие страницы. А если “нет такой книги”, то и смысла искать другие страницы просто нет.


            1. ws233
              05.05.2026 06:53

              Это 2 разных уровня обработки с разной детализацией. Они не взаимоисключающие. Наоборот...

              Реализация на основе HTTP-кодов может быть одна универсальная на все приложение. Да, будут проблемы с точностью описания ошибки. Зато дешево и сердито.

              Если нужно дорого и детально прорабатывать, то пожалуйста - можно подниматься на уровень выше (или спускаться на уровень ниже, смотря как располагать уровни) и детализировать.

              В этом смысл уровней. Есть один универсальный обработчик, если он не устраивает, дописываете конкретные обработчики на более высоких (низких уровнях) уровнях. За доп.деньги. При этом универсальный код все так же продолжает работать, если вдруг Вы забыли написать более конкретные вещи…

              Поэтому заменять обработку HTTP-кодов джейсоном всегда – в корне неверно. Вы заранее обрекаете себя всегда на излишние затраты. Наоборот, правильно делать общие обработчики и потом их дополнять деталями, если это кому-то будет нужно и кто-то это оплатит...


              1. stranger_shaman
                05.05.2026 06:53

                иными словами, вы предлагаете сделать тот же самый функционал, но сложнее и дороже.


                1. ws233
                  05.05.2026 06:53

                  HTTP-коды? Они ж уже реализованы и доступны из коробки на любой платформе. Остается только научиться ими пользоваться. И тогда, да, реализовывать дубль обработки кодов на соседнем уровне не придется.

                  Только в конкретных узких случаях, что сильно дешевле, чем продублировать всю систему, связать её независимые сервисы в один клубок, и обязать все связанное теперь обрабатывать ошибки настолько детально, насколько это необходимо одному единственному сервису. А Вы предлагает вот так действовать и считаете, что это будет дешевле и проще?


                  1. Rsa97
                    05.05.2026 06:53

                    HTTP-коды? Они ж уже реализованы и доступны из коробки на любой платформе.

                    Да? Как мне их получить на WebSocket? Ведь REST не прибит гвоздями к HTTP.


            1. whoisking
              05.05.2026 06:53

              Если вам сильно надо, то можно использовать 400 с подробным ответом


    1. daskuncik Автор
      05.05.2026 06:53

      Привет!
      Спасибо за комментарий.
      Для случая /api/book/350/page/12 одной ошибки 404 точно будет мало, тк влечет неопределенность, которую мы дополняем типизацией ошибки в теле ответа. а Для случая /api/orders/999 отвечт не будет двойственным, поэтому тело можно не прикреплять.

      Ошибка 403 дословно читается как "я понял, кто вы, но вам туда нельзя". Тип запрета зависит от вашего сервиса: если у вас книжный магазин, то покупателю нельзя производить никакие изменения в книгах, поэтому дополнительных пояснений к ошибке 403 не понадобится. А если у вас портал, где есть читатели и авторы, которые могут редактировать свое, а чужое не могут, тогда вас спасет описание ошибки в теле.

      Про "клиенту приходится проверять и статус, и поле в теле". Здесь скорее о простых случаях, когда статус МОЖЕТ сказать сам за себя и доп.поле не требуется. В моей практике такие случаи нечасты.

      При использовании HTTP и WebSocket понятие единого интерфейса не применимо


      1. Rsa97
        05.05.2026 06:53

        Ну так если мы всё равно добавляем поле с ошибкой в ответ и оно более информативно, чем статус HTTP, то есть смысл для унификации всегда добавлять такое поле. И не гадать, где оно есть, где его нет.

        если у вас книжный магазин, то покупателю нельзя производить никакие изменения в книгах, поэтому дополнительных пояснений к ошибке 403 не понадобится

        А если это был GET, то куда ему нельзя, в книгу вообще или именно на 12 страницу?

        При использовании HTTP и WebSocket понятие единого интерфейса не применимо

        А вот если использовать свою систему ошибок, не полагаясь на протокол, то единый интерфейс вполне себе возможен. Просто в WebSocket будет небольшой оверхед на id запроса и глагол (действие). А вся остальная часть будет той же самой.


        1. ws233
          05.05.2026 06:53

          А вот если использовать свою систему ошибок, не полагаясь на протокол, то единый интерфейс вполне себе возможен. Просто в WebSocket будет небольшой оверхед на id запроса и глагол (действие). А вся остальная часть будет той же самой.

          Как-будто и используя систему ошибок HTTP тоже можно...

          Вы ж просто расширите WebSocket транспортными ошибками, нет? Их можно придумать свои, а можно взять готовые из HTTP – в чем разница-то?

          Как-будто это и есть рабочая, правильная и рекомендованная схема. Ошибки получаются разделенными на 2 уровня - транспортные и логические. При этом они могут быть использованы с любым транспортом (при расширении транспорта, не поддерживающего эти ошибки). Сами ошибки в такой схеме тоже генерируются и обрабатываются на двух разных уровнях и друг с другом не смешиваются, что хорошо.

          Как-будто Вы с автором статьи говорите ровно об одном и том же, но разными словами.


          1. Rsa97
            05.05.2026 06:53

            Как-будто и используя систему ошибок HTTP тоже можно…

            Можно, но недостаточно информативно. Скажем в ответ на GET /api/book/350/page/12 вам вернулся HTTP-ответ 403 Forbidden. К чему вам запрещён доступ - к API в целом, к книге или к странице?

            Вы ж просто расширите WebSocket транспортными ошибками, нет?

            Зачем в WebSocket транспортные ошибки из HTTP, если достаточно использовать ошибки бизнес-логики?

            Как-будто Вы с автором статьи говорите ровно об одном и том же, но разными словами.

            Не совсем. Автор статьи предлагает использовать статусы HTTP и только если их недостаточно, то добавлять расширенную информацию. Я же за то, чтобы всегда использовать бизнес-ошибки, а транспортные ошибки оставить транспорту ну или, если хочется, использовать их параллельно.


            1. ws233
              05.05.2026 06:53

              Зачем в WebSocket транспортные ошибки из HTTP, если достаточно использовать ошибки бизнес-логики?

              Вот тут ответ на этот вопрос. От общего к частному всегда дешевле, чем сразу все утыкивать частностями без необходимости...


              1. Rsa97
                05.05.2026 06:53

                Ну так у вас так и так есть какие-то обработчики ошибок бизнес-логики. Где-то же вы проверяете, есть ли у пользователя доступ к API, книге и странице. Что вас заставляет возвращать одну и ту же неинформативную ошибку для трёх разных вариантов? Ну а хочется озвращать ещё и транспортную, так добавьте параметр в обработчик ошибок, будут там бизнес-код и транспортный код ошибки.


                1. ws233
                  05.05.2026 06:53

                  У меня нет "каких-то обработчиков ошибок бизнес-логики". У меня, допустим, есть один конкретный обработчик описанной Вами ситуации со страницей в одном единственном месте, в одном единственном сервисе, предоставляющем страницы книги.

                  Во всех остальных 99 местах у меня даже обработчика нет, я пользуюсь доступным из коробки, из системы, от HTTP.

                  Вы мне предлагаете этот единственный конкретный обработчик бизнес-логики сервиса страницы сделать общим для всех моих 9 сервисов и впихнуть его, например, в сервис аккаунта пользователя. Верно я Вас понял? Зачем сервису аккаунта пользователя знать что-то про ошибку бизнес-логики сервиса страниц? И вообще детали того, как обрабатываются конкретные ошибки бизнес-логики в соседнем сервисе? А если коды ошибок бизнес-логики совпадут в разных сервисах?

                  А еще мы не рассмотрели варианты, когда транспортные коды приводят к перезапросам, переавторизациям, обновлениям токенов, маршрутизации или покупке подписок. Не надо мух с котлетами – в одну сковородку...

                  Повторю ответ на Ваш вопрос. Меня заставляет разделять ошибки транспорта и логики – экономия и эффективность. Имхо, этот путь дешевле.

                  Вот тут об этом же. Но если есть цифры, какой из путей дешевле и проще, буду рад с ними ознакомиться.


                  1. Rsa97
                    05.05.2026 06:53

                    У меня, допустим, есть один конкретный обработчик описанной Вами ситуации со страницей в одном единственном месте, в одном единственном сервисе, предоставляющем страницы книги.

                    То есть в остальном месте у вас нет бизнес-логики? И если я запрошу /api/book/350 то ваш сервер не будет проверять, есть ли у меня права доступа к API и книге?

                    Зачем сервису аккаунта пользователя знать что-то про ошибку бизнес-логики сервиса страниц?

                    А зачем ему это знать? У него свои ошибки. Но, опять же, вы ведь проверяте доступ пользователя к API пользователей и к конкретному запрошенному пользователю? То есть место для выдачи разных бизнес-кодов ошибок у вас уже есть. А транспортный код может быть один, а может его и вовсе нет в используемом транспорте.


                    1. ws233
                      05.05.2026 06:53

                      Ну, вот я (да и вроде сообщество) не относят аутентификацию и авторизацию к бизнес-логике. Это логика уровня безопасности или управления доступами. Об этом я и говорю, что конкретно эта часть должна быть отделена от бизнес-логики и обрабатываться отдельно. В HTTP оно как раз в уровне транспорта и осуществляется. Как и прочие редиректы, кеширование и другие свистопляски...

                      Это место будет одно, да. Но оно в HTTP уже реализовано, зачем мне его повторять? Чтоб показать, какой я крутой? Мой бизнес и так это знает и лишний раз за это платить не хочет :) Так что я лично предпочитаю так за чужой счет не самоутверждаться.

                      Если этого же функционала для WebSocket нет, то придется его реализовать. Но опять же, не стоит смешивать это с бизнес-логикой. Это другой уровень...

                      Так понятнее?


                      1. Rsa97
                        05.05.2026 06:53

                        Насчёт аутентификации соглашусь, она слабо связана с бизнес-уровнем и может быть отдельным слоем. Хотя этот слой тоже может быть шире, чем один HTTP-статус 401.
                        А вот авторизация вне бизнес-уровня особого смысла не имеет. Если какой-либо роли пользователя не разрешено изменять конкретное поле в конкретном типе документа при конкретном его статусе, то где это проверять, на уровне транспорта apache/nginx?


                      1. ws233
                        05.05.2026 06:53

                        Вопрос не менее дискуссионный, чем те, что мы обсуждали выше и ниже :) но HTTP присылает код 403 в этом случае вроде? Почему-то HTTP решил, что это транспортный уровень – видимо, были причины. Возможно потому, что проверка осуществляется все же еще ДО входа в слой бизнес логики.

                        Проверки авторизации, кажется, реализуются, как и аутентификация по шаблону проектирования "Прокси", т.е.еще до передачи управления в бизнес-логику осуществляют все необходимые проверки, возможно с запросом бизнес-логики, но точно без передачи ей управления... Прокси – это как раз доп.слой перед уровнем логики. В чистом виде.


                      1. k4ir05
                        05.05.2026 06:53

                        Даже на транспортном уровне нужно разделять 401 от 403. В первом случае значит, что нужно пройти определённую процедуру и можно повторить этот запрос. Если, например, настроена http basic authentication, то в ответе будет соответствующий заголовок, тогда браузер запросит логин и пароль, и повторит запрос. Могут быть и другие способы. И они могут осуществляться и веб-сервером без участия приложения.

                        А 403 говорит что эта операция клиенту с этими учётными данными запрещена, и повторять запрос не надо.


                      1. Rsa97
                        05.05.2026 06:53

                        возможно с запросом бизнес-логики

                        Транспортному уровню вообще не должно быть дела до бизнес-логики. Его дело - принять от клиента запрос, отправить его маршрутизатору (промежуточный слой), получить от маршрутизатора ответ (или ошибку) и отправить его клиенту.


                      1. ws233
                        05.05.2026 06:53

                        Ну, по-хорошему именно так. Авторизация и не должна идти в бизнес-логику, соглашусь. Она должна через отдельный сервис определить, какая страница какому клиенту доступна, а какая — нет.


                      1. Rsa97
                        05.05.2026 06:53

                        А как заранее определить, что клиенту недоступно конкретное действие над конкретным элементом конкретного документа, если это определяется правилами бизнес-логики?


    1. k4ir05
      05.05.2026 06:53

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

      Но, чтобы понять, что именно произошло, клиенту и так придётся откуда-то эту информацию брать

      HTTP статус не только для клиента. Это ещё сигнал промежуточным участникам (браузеру, серверам и пр.), можно ли кэшировать ответ, можно ли повторять запрос и т.п.


      1. ws233
        05.05.2026 06:53

        Именно! И если не использовать HTTP-статусы, то всю эту логику придется ручками повторять в приложении. А её там немало...


      1. Rsa97
        05.05.2026 06:53

        Это ещё сигнал промежуточным участникам

        То есть статус транспортного уровня. Ну так давайте оставим транспортный уровень транспорту. Тем более, что кэширование управляется не только HTTP-статусом (за исключением 1xx), но и дополнительными полями ответа (RFC 9111). И при желании можно включить кэширование ответа с любым финальным статусом. Ну и не забываем, что REST - это не только про HTTP, но и про любой другой протокол, в том числе и не имеющий никаких встроенных статусов.


        1. k4ir05
          05.05.2026 06:53

          Не только транспортного. В приложении он тоже доступен. Зачем от него там отказываться?

          И при желании можно включить кэширование ответа с любым финальным статусом

          А работать оно будет? Браузер закэширует 500-й? Это было очень не разумно.

          Ну и не забываем, что REST - это не только про HTTP, но и про любой другой протокол, в том числе и не имеющий никаких встроенных статусов.

          Так его придумывали именно под HTTP. На какой ещё протокол его можно применить?


          1. Rsa97
            05.05.2026 06:53

            Браузер закэширует 500-й?

            По стандарту должен, если с ним придёт соответствующий Cache-Control. А 501 вообще по умолчанию кэшируемый.

            На какой ещё протокол его можно применить?

            Да на любой. Основная идея REST - это управление ресурсами через их полные или частичные представления. Вот вам REST запрос поверх JSON-RPC через WebSocket:

            {
              "jsonrpc": "2.0",
              "id": 1,
              "method": "PATCH",
              "params": {
                "resource": "user/999",
                "representation": { "state": "banned" }
              }
            }
            


            1. ws233
              05.05.2026 06:53

              Ну, и отлично. Реализуйте на WebSocket'ах ту часть, что реализована на HTTP. Кеширование, редиректы, аутентификации, авторизации и проч. (Если оно, конечно, нужно, хотя сама идея выглядит почему-то сомнительно и стоит тут откатиться назад и подумать зачем же дублировать REST на WebSocket и нельзя ли эту же задачу решить как-то по другому, без этого "характерного запаха").

              Логическую-часть оставьте отдельно и независимо. Она будет одна на оба транспорта.

              Какой смысл объединять логическую часть с той частью HTTP, которая не реализована в WebSocket? Только потому что Вы её вынуждены реализовывать? Ну, ок. А стоит это сколько? Вы точно сможете HTTP-часть без ошибок и полностью повторить? А зачем повторять, если она уже есть? Используйте готовую реализацию там, где она есть - проблем хотя бы на этом транспорте не будет. Будете с ней сравнивать свою реализацию для WebSocket, как с эталоном. Еще и пошарите в OpenSource, когда закончите. Реализация будет не завязана на детали внутренней логики именно Вашего приложения и просто будет дублировать стандарт HTTP (хоть это и сомнительно само по себе), поэтому её сможет переиспользовать любой.

              Так вышло, что REST – не протокол, не стандарт, это лишь архитектура, если верить Википедии. А если это архитектура, то она может быть составлена из разных слоев (транспортного и логического). Так вышло, что стандартные реализации есть для HTTP (на всех слоях), но их нет для WebSocket (или есть? JSON-RPC разве, как раз, не оно?) – ну, реализуйте сами. Но по архитектуре, а не все на логическом уровне. Реализация только на логическом уровне не будет REST, если верить Википедии и такому её пониманию. Но я не претендую на истинность этого понимания. Просто высказываю свою точку зрения.

              Применительно к вашему джейсону все, что в representation будет обрабатываться логическим уровнем, все остальное (что выше) – транспортным. Разделяй и властвуй. Я лишь об этом...

              Что думаете по этому поводу? Только давайте без очередных отсылок на WebSocket или неконсистентные коды ошибок. Вроде это мы уже слышали. Раскрывайте свои мысли, пожалуйста.


              1. Rsa97
                05.05.2026 06:53

                Сам по себе JSON-RPC это не REST, это именно RPC-протокол, передаваемый любым транспортом.

                Применительно к вашему джейсону все, что в representation будет обрабатываться логическим уровнем, все остальное (что выше) – транспортным.

                Не совсем. Действие (PATCH) и ресурс - это тоже может быть бизнес-уровнем. Мы должны знать, разрешено ли текущему пользователю данное действие над данным полем данного ресурса, причём это может зависеть от текущего состояния ресурса (например, утверждение документа закрывает правки для всех, кроме руководителя). А это права/роли пользователей.

                А я, собственно, пишу о том, что стоит разделять транспортный и бизнес-уровни, в том числе и в статусах ошибок, хотя никто не мешает задействовать и одновременно оба уровня. Ну а оппоненты утверждают, что “HTTP-статуса 403 хватит для любой ошибки авторизации”.


            1. k4ir05
              05.05.2026 06:53

              Вот вам REST запрос поверх JSON-RPC через WebSocket

              Ну это уже сильно отходит от изначального концепта REST. Если не ошибаюсь, он подразумевает stateless протокол. А WebSocket явно не такой.


              1. Rsa97
                05.05.2026 06:53

                WebSocket вполне себе может работать как stateless. То, что в нём на транспортном уровне есть постоянное соединение, не означает наличия состояние. В HTTP постоянное соединение тоже можно использовать (Connection: keep-alive начиная с HTTP/1.1).


                1. k4ir05
                  05.05.2026 06:53

                  Но там ведь используется одно и то же подключение (сессия). И все запросы осуществляются в рамках этой сессии. Я, может, что-то не так понимаю, но stateless означает, что каждый запрос хранит всю информацию для его обработки и изолирован от других запросов. То есть, разные запросы могут обрабатываться разными экземплярами сервера. Что позволяет осуществлять балансировку запросов при горизонтальном масштабировании. А как это реализовать с WebSocket? Он же работает в рамках одного HTTP запроса (с переключением протокола). И сессия привязана к одному серверу.

                  То, что в нём на транспортном уровне есть постоянное соединение, не означает наличия состояние

                  А как же аутентификация? В каждом запросе отправляется jwt (или что-то подобное)?


                  1. Rsa97
                    05.05.2026 06:53

                    Но там ведь используется одно и то же подключение (сессия).

                    В HTTP вы тоже можете использовать одно подключение для серии запросов. Это не делает его сессионным. Вопрос в том, используются ли в обработке запроса данные этого соединения.

                    То есть, разные запросы могут обрабатываться разными экземплярами сервера

                    Это преимущество, которое даёт stateless, а не требование. К тому же, на входе вполне может стоять один балансировщик, который далее распределяет запросы между серверами/сервисами.

                    А как же аутентификация? В каждом запросе отправляется jwt (или что-то подобное)?

                    Да. Для случая JSON-RPC просто добавляем ещё одно поле в params.


  1. ws233
    05.05.2026 06:53

    Спасибо за статью.

    Позволю себе добавить в миф про кеширование. На мобилах очень популярны библиотеки кеширования – огромное множество библиотек, которые слоем выше HTTP на клиенте полностью дублируют функционал, который уже имеется в HTTP из коробки. Например, SDWebImage для кеширования картинок на iOS. Для меня, например, такие библиотеки – сигнал, что команда (серверные и клиентские разработчики и аналитик) совершенно не проработала свой функционал. Соответственно, библиотеки эти из мобил надо выпиливать нещадно, а команду обучать лучшим практикам кеширования, встроенным в любую систему и сам протокол HTTP.


    1. k4ir05
      05.05.2026 06:53

      Не туда.


  1. k4ir05
    05.05.2026 06:53

    Миф 0. Авторизация = аутентификация.

    Пользователь авторизован, но не имеет прав — это ...

    антонимы.

    Четко разделяет:кто ты? — 401;можно ли тебе это? — 403.

    Не совсем понятно. Ошибки это же не вопросы, а ответы.

    401 - ты не тот, за кого себя выдаёшь / ты не зарегистрирован.

    403 - тебе это нельзя.