Привет всем! Меня зовут Андрей, и я разработчик. На своей практике я успел столкнуться с разными протоколами. И, конечно же, были холивары в команде — какой и почему выбрать. Адепты подхода 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)
Tsimur_S
18.04.2023 08:44+2TL;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.
Как кухонный топор для рубки мяса, дома есть у каждого.Просто внедрять, дебажить и тестировать.
AlekseyArh
18.04.2023 08:44+1Не-REST запросы сложно будет покрыть кэширующим слоем через nginx например, потому что прямой путь /api/user/{id} превращается в абстрактный /api/ где user/{id} в теле запроса может отличаться. Сложно будет понять что можно кэшировать, а что нет.
MaxLevs
18.04.2023 08:44Исключает использование в браузерах из-за HTTP/2? Очень странно. Я вот запускаю swagger через браузер и вижу в логах сервера, что запросы от swagger приходят по HTTP/2.
aleks_raiden
18.04.2023 08:44Мощно вы записали сразу JSON-RPC что никто не использует, при том, что в ряде индустрий это прям основной протокол.
paamayim
18.04.2023 08:44Graphql имеет смысл использовать, если у вас nocode бекенд, отсутствует бекендер, или вы хотите его разгрузить. Например он будет заниматься интеграциями а вы пилить crud-ы. В таком случае вы существенно ускорите разработку. Ещё одним неоспоримым преимуществом является типизация, которую не всегда вам может обеспечить условный бекендер питонщик. Нынешние graphql платформы вроде hasura и apollo могут также предложить неплохую производительность по сравнению с бекенд api. Если же ваш проект это только crud-ы и у вас есть свободный бекендер то вы можете наоборот потерять в скорости так как вся работа по получению и обработки данных переносится на ваши плечи(как фронтендера)
Uint32
А как же grpc-web ?
Tsimur_S
>А как же grpc-web ?
Он работает через прокси сервер транслирующем запросы и на данный момент может не поддерживать разные фичи типа bidirectional streaming.