Привет всем! Меня зовут Андрей, и я разработчик. На своей практике я успел столкнуться с разными протоколами. И, конечно же, были холивары в команде — какой и почему выбрать. Адепты подхода REST спорят с GraphQL-щиками. А поклонники gRPC тихо смеются над ними. Давайте все разложим по полочкам.

В зависимости от системы, ограничений и личных предпочтений команды понадобятся самые разные способы общения и передачи данных. REST, GraphQL, RPC и другие — сегодня разберемся во всем многообразии протоколов, где и зачем они используются.

Что такое протокол?

Простыми словами, это общепринятый способ / формат общения систем между собой. Протоколов довольно много — протоколы общения с устройствами (USB, Bluetooth), протоколы межпроцессного взаимодействия (IPC), протоколы взаимодействия с удаленными компьютерами (SSH, FTP), протоколы отправки и получения почты (SMTP, POP3). Мы будем говорить только о веб-протоколах — способах общения фронтенда (мобильный или веб-клиент) с бэкендом или сервисов между собой. 

Большинство веб-протоколов работают поверх HTTP. Прежде чем говорить о других протоколах, расскажем, на основе чего они все строятся и работают:

Примечание: мы не будем разбирать модель TCP/IP, на которой работает сеть Интернета. Для рассмотрения протоколов передачи данных достаточно понимать, что HTTP работает поверх TCP/IP. Если вам стало интересно, то по ссылке можете углубиться в эту тему.

Введение: как работает протокол HTTP 

В разработке основным протоколом передачи данных является HTTP. Он относится к прикладным протоколам и работает поверх других протоколов сетевого стека. HTTP — это простой текстовый протокол для передачи любого контента. Изначально разработан для передачи HTML-файлов (в те времена, когда HTML был языком разметки научной и технической документации в CERN).

Как правило, в вебе мы используем надстройки поверх протокола. HTTP работает поверх TCP — один из транспортных протоколов, который определяет, как данные будут передаваться от компьютера к компьютеру. В случае с TCP «отправитель» будет ожидать ответа, что «получатель» принял пакет, прежде чем отправить следующий. Если «отправитель» не дождался подтверждения — отправит пакет еще раз. 

 Для передачи данных используются HTTP-пакеты. Сам пакет в HTTP представляет собой текстовое сообщение со следующей структурой:

<HTTP-метод> <Путь ресурса> HTTP/1.1

<Заголовки>

<Тело запроса>

, где 

  • HTTP-метод — это глагол, который определяет, какую операцию мы хотим выполнить (GET, POST, PUT и т.д.);

  • Путь — URL до необходимого нам ресурса;

  • Версия протокола — чаще всего указывается HTTP/1.1;

  • Заголовки — дополнительные параметры запроса, которые нужны серверу;

  • Тело запроса — данные, которые мы хотим передать.

Например, выполним POST-запрос на создание пользователя Вася, который не является администратором в системе. Причем укажем, что данные передаются в формате JSON, а ответ мы ожидаем на русском языке.

POST /users HTTP/1.1
Content-Type: application/json
Accept-Language: ru

{"name": "vasya", "is_admin": false}

Эти термины пригодятся, когда мы будем говорить о других протоколах. Без контекста HTTP непонятно, что такое пути, заголовки, тело и статусы ответов. Часть протоколов использует все, а часть нет. 

Начнем мы с одного из старейших подходов в вебе — REST, который использует все возможности HTTP. Потом пройдемся по GraphQL, которому предрекали судьбу полностью вытеснить REST. Далее посмотрим на семейство RPC-протоколов. И WebSocket — альтернативный подход во взаимодействии с бэкендом, ориентированный на постоянную связь клиента и сервера.

REST

REST — Representational state transfer — архитектурный подход, который описывает набор правил, как организовать общение систем. У данного подхода есть ряд требований — отсутствие состояния, единообразие интерфейсов. В простом понимании это URI, который описывает, что за ресурс мы запрашиваем: список элементов или один элемент. Кроме того, используются HTTP-глаголы или HTTP-методы — какие действия мы произведем с ресурсом.

Методов достаточно много. Как правило, мы имеем дело с:

  • POST /users для создания нового пользователя;

  • GET /users для получения списка пользователей;

  • GET /users/1 для получения одного пользователя с ID=1;

  • PUT или PATCH /users/1 для изменения пользователя;

  • DELETE /users/1 для удаления.

В ответе от сервера мы получаем запрошенный ресурс / сообщение об ошибке и трехзначный код ответа. Код ответа показывает, что произошло с запросом пользователя и что, в случае ошибки, пошло не по плану. Например:

  • 200 – запрос выполнен успешно;

  • 401 – необходимо авторизоваться;

  • 404 – ресурс не найден;

  • 505 – сервер временно недоступен. 

С приходом JSON, как основного формата общения, требования к системам начали расти. Росли и требования к REST. Так появилась спецификация HATEOAS — дополнение требований. Теперь вместе с возвращаемым ресурсом мы можем получить информацию о том, какой набор действий предоставлен пользователю с этими ресурсами. Так, если мы делаем запрос GET /users, то получим список пользователей и: 

  • количество страниц, на которые разбит этот список;

  • ссылки на предыдущую и следующую страницы;

  • в каждом элементе списка будет ссылка на конкретного пользователя.

Но и эта система получила дальнейшее развитие в лице JSON:API. Подход в свое время получил приз зрительских симпатий в категории «Название, которое максимально неудобно гуглить». Изначально этот формат использовался во фреймворке Ember для общения с бэкендом. Потом был опубликован отдельной спецификацией:

  • в JSON:API была добавлена группировка GET-параметров по смыслу. Вместо page_size и page_number предлагалось использовать page[size] и page[number] или page[offset]

  • предлагался единый формат ответа:
    {"data": данные, "error": ошибка}

  • добавлен единый формат самих ресурсов:
    {id, type, attributes, links}

  • поддержка зависимых ресурсов, что помогало получать несколько связанных ресурсов в одном запросе, а не частями. Пользователя можно получить вместе с его постами, а посты — с комментариями.

Так REST развился до современного состояния. Решения на основе HATEOAS или JSON:API используются не везде и не всегда. Пока что REST-подход остается наиболее распространенным в современном вебе. Хотя в один момент даже начали говорить о его скорой смерти.

GraphQL

В 2015 году Facebook опубликовал свой новый язык общения фронтенда с бэкендом. Уже к 2019 году некоторые веб-разработчики начали говорить, что GraphQL вытеснит REST.

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

В отличие от REST, у нас есть единая точка доступа к приложению — /graphql. На нее мы отправляем все наши запросы. Язык позволяет писать запросы к ресурсам в виде графа зависимостей, где зависимостью может быть поле или связанная сущность:

{
  users {
    id
    name
    age
    email
    posts {
      id
      title
      body
    }
  }
}

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

Как и любой формат общения, GraphQL вводит ряд понятий:

  • запрос (query);

  • обработчик (resolver) — функция, отвечающая за выдачу данных по запросу;

  • схема (schema) — описание структуры данных в рамках GraphQL;

  • мутация (mutation) — тот же обработчик, только на изменение данных.

Авторы GraphQL позиционируют его как protocol-agnostic, то есть он не привязан к конкретному протоколу. Это позволяет использовать его и поверх HTTP, и поверх WebSocket, о котором пойдет речь дальше.

Рекомендую прочитать материал REST API vs GraphQL. В нем разобрано, что действительно стоит за их сравнением, нужно ли срочно заменить REST на GraphQL, и как подойти с головой при выборе архитектуры общения фронта и бэка.

RPC

У семейства протоколов RPC (Remote Procedure Call) свой отличительный подход — удаленный вызов процедур. В отличие от REST, одна точка входа, и телом запроса определяется, какой ресурс или какое действие будет выполнено. Реализаций подхода не так много. Главным образом они различаются форматом передачи данных — XML, JSON или бинарный.

XML-RPC и SOAP

XML-RPC изначально был разработан Microsoft в конце 90-х годов. Это текстовый протокол, в изначальном виде довольно прост в освоении. Единственная проблема этого формата — это сам XML (eXtensible Markup Language). XML, как формат передачи данных, довольно избыточен. В первую очередь, из-за открывающих и закрывающих тегов. У HTML в каком-то смысле та же проблема, но ничего другого для верстки не существует.

Позже Microsoft разработали еще один протокол, который стал расширением XML-RPC — SOAP (Simple Object Access Protocol). У него более строгая структура, много ограничений и требований. Сам протокол может работать поверх множества сетевых протоколов – SMTP, FTP и тд. 

Сейчас SOAP используется для общений между сервисами (в основном с 1С) и для отправки SMS. Некоторые сервисы вроде интернет-магазинов до сих пор используют его для внутренних нужд — для SOAP один раз описывается схема передачи данных, дальше большинство SOAP-клиентов сами могут сформировать запрос и ответ без дополнительных действий со стороны разработчика.

JSON-RPC

JSON-RPC — это протокол семейства RPC, у которого в качестве формата передачи данных используется JSON. Типичный запрос выглядит так: 

POST /rpc 

{
  "method": "getSomeData",
  "params": ["list", "of", "params"],
  "id": 1
}

, где method — это имя вызываемой процедуры; params — список аргументов процедуры; id — уникальный идентификатор запроса, который генерируется запрашивающей стороной.

Несмотря на простоту и внешнее удобство, не получил популярности. В том числе из-за REST API с поддержкой JSON в качестве формата передачи данных. 

Но не так давно в React сообществе появился новый формат общения между разными частями приложения, построенного на фреймворке Next.js – tRPC. Является версией JSON-RPC с поддержкой типизации из TypeScript. Протокол относительно молодой, основное применение и развитие происходит пока только внутри React (Next.js) сообщества. Но внимания заслуживает.

В остальном же JSON-RPC популярность не снискал и практически не используется.

gRPC

gRPC — это бинарный протокол, т.е. данные передаются в бинарном виде, а не в виде текста. Разработан в Google и изначально использовался только для унификации взаимодействий между сервисов внутри самой компании. В 2016 году был выпущен в публичный доступ. 

Для кодирования и декодирования сообщений используется собственный протокол сериализации Protobuf (Protocol Buffers). Максимально похожий на структуры из языка Си. Из плюсов Protobuf выделяют компактность, скорость сериализации и десериализации (особенно в сравнении с XML-форматами). Для описания формата сообщений и обработчиков пишутся *.proto файлы. Потом эти файлы компилируются в язык, на котором пишется приложение — Java, Python, PHP, JavaScript, Go и многие другие. Стоит отметить, что в Go-среде получил наибольшее распространение.

Главным ограничением протокола является, что он работает поверх HTTP/2. Это полностью (на данный момент) исключает его использование в браузерах. Поэтому gRPC — протокол исключительно для общения сервисов на стороне бэкенда. Протокол очень популярен. Поэтому, если вы не столкнетесь с ним в первый год работы, иметь о нем представление будет полезно.

WebSocket

WebSocket — это протокол двусторонней связи для постоянного обмена сообщениями клиента и сервера. Как и HTTP, WebSocket работает поверх TCP. Но вместо периодического соединения формата «запрос – ответ», держит постоянное соединение с сервером. Поэтому сервер всегда знает конкретного клиента «в лицо» и может отправить ему данные без дополнительного запроса. Например, используется для систем оповещений и чатов браузерных игр.

До появления полноценного стандарта подобные задачи решались двумя способами. Первый, периодические запросы «у меня есть новые сообщения» — но этот способ фактически мертв. Второй, Long-polling запросы — сервер не ограничен по времени, в течении которого он должен отдать ответ. Когда сервер получает запрос, он ответит на него, когда данные будут доступны для отправки. А браузер, в свою очередь, как только получит эти данные, сразу же делает новый запрос. Для конечного пользователя это выглядит как постоянное соединение с сервером.

В отличие от gRPC, вам не нужно будет изучать какой-то специфический формат обмена данными. WebSocket использует свой собственный бинарный формат, внутри которого вы можете передавать что угодно в удобной для вашего приложения форме.

Общение через WebSocket может быть реализовано по принципам REST — HTTP-метод + ресурс + тело запроса. Или реализовано, как JSON-RPC — имя процедуры + список параметров. Либо вовсе использовать GraphQL — называется «подпиской» (subscription).

Самое частое применение WebSocket — real-time чаты. Новое сообщение получатель видит, когда сервер рассылает сообщения всем адресатам, а не когда запрашивает сам. Библиотеки для WebSocket-сервера существуют почти для всех языков и их фреймворков. Вопрос будет только в выборе самого популярного и/или удобного лично для вас.

Отлично! Что с этим теперь делать, и где использовать?

Если вы начинающий специалист, изучите сначала REST. Не важно, как вы себя позиционируете — бэкенд, фронтенд, мобайл, аналитик или тестировщик. REST — самый распространенный. Поэтому знать его принципы обязательно.

Потом, если вы занимаетесь бэкендом, посмотрите в сторону gRPC. Сейчас он все чаще появляется в проектах и пытается вытеснить REST в общении сервисов. И посмотрите GraphQL, особенно если занимаетесь фронтендом. Он здесь становится все популярнее.

Дальше задайте себе вопрос: «Что лучше решает задачу?». Большинство задач может решить REST. Задачи по быстрому межсервисному взаимодействию возьмет на себя gRPC. Оставшуюся часть могут покрыть SOAP или GraphQL. Например, SOAP будет задействован в общении со складскими системами — 1С и похожие. С помощью GraphQL на клиентской стороне будут запрашиваться данные о профиле пользователя. В остальном дело будет стоять за командой, руководителем и вашим собственным выбором.

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

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


  1. Uint32
    18.04.2023 08:44

    Главным ограничением протокола является, что он работает поверх HTTP/2. Это полностью (на данный момент) исключает его использование в браузерах.

    А как же grpc-web ?


    1. Tsimur_S
      18.04.2023 08:44

      >А как же grpc-web ?

      Он работает через прокси сервер транслирующем запросы и на данный момент может не поддерживать разные фичи типа bidirectional streaming.


  1. Tsimur_S
    18.04.2023 08:44
    +2

    TL;DR

    Если нужен чат или браузерная mmo расмотрите websocket, только сразу продумайте как будете балансировать это на бекенде. В целом так же это неплохое решение когда вам нужен легковесный протокол один-ко-многим.

    Если вы хотите создать нерушимый контракт между двумя бекендами в виде единого файлика то используйте grpc.

    Если вам критически важно число RPS между бекендами и вы не используете node.js(на этой платформе разницы между бинарным протоколом и REST over HTTP2 практически не будет) а так же скорее всего Python/Ruby/PHP, то опять же выбирайте grpc.

    Если в описании проекта часто встречается рядом Java и Enterprise и не встречаются слова Android, iOS а корни проекта берут начало из нулевых или конца 90x то берите SOAP. Если там где-то по тексту встречается IBM или mainframe то точно берите, не прогадаете, могут еще что похуже впихнуть.

    JSON-RPC - не видел что бы использовали в естественной среде обитания. Кому нужен стандарт если всегда можно поломать рамки REST своим RPC-велосипедом.

    GraphQL - хорошо расписан у автора. REST велосипедостроение с api/users?...&fields=[name,surname] возвели в стандарт. Возможно хороший выбор для SPA фронтендеров.

    Во всех остальных случаях лучше выбрать обычный REST. Как кухонный топор для рубки мяса, дома есть у каждого. Просто внедрять, дебажить и тестировать.


  1. AlekseyArh
    18.04.2023 08:44
    +1

    Не-REST запросы сложно будет покрыть кэширующим слоем через nginx например, потому что прямой путь /api/user/{id} превращается в абстрактный /api/ где user/{id} в теле запроса может отличаться. Сложно будет понять что можно кэшировать, а что нет.


  1. MaxLevs
    18.04.2023 08:44

    Исключает использование в браузерах из-за HTTP/2? Очень странно. Я вот запускаю swagger через браузер и вижу в логах сервера, что запросы от swagger приходят по HTTP/2.


  1. aleks_raiden
    18.04.2023 08:44

    Мощно вы записали сразу JSON-RPC что никто не использует, при том, что в ряде индустрий это прям основной протокол.


  1. paamayim
    18.04.2023 08:44

    Graphql имеет смысл использовать, если у вас nocode бекенд, отсутствует бекендер, или вы хотите его разгрузить. Например он будет заниматься интеграциями а вы пилить crud-ы. В таком случае вы существенно ускорите разработку. Ещё одним неоспоримым преимуществом является типизация, которую не всегда вам может обеспечить условный бекендер питонщик. Нынешние graphql платформы вроде hasura и apollo могут также предложить неплохую производительность по сравнению с бекенд api. Если же ваш проект это только crud-ы и у вас есть свободный бекендер то вы можете наоборот потерять в скорости так как вся работа по получению и обработки данных переносится на ваши плечи(как фронтендера)