Привет! Меня зовут Максим Соколов, я — аналитик в команде “Управление доступностью товаров и категорий”. В нашей команде была выделена отдельная подгруппа, которая создавалась специально под новый продукт-фичу для селлеров. Сразу стало понятно, что для реализации нового функционала требуется разработка нового микросервиса. Командой разработки было принято решение интегрироваться по gRPC, но мне до конца не было понятно, почему выбор именно такой. И тут я решил разобраться подробнее!

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

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

На свете много статей про проектирование API, в которых описаны архитектурные стили, протоколы, технологии. Информации — огромное количество, иногда она даже противоречивая, поэтому новичку тяжело подступиться к теме.

В этой статье я хочу дать точку входа для джун/мидл системных аналитиков, которые хотят разобраться в межсервисной интеграции. Мы пройдёмся по HTTP, REST, RPC и gRPC, разберёмся в их значениях. Выясним, почему эти аббревиатуры появляются, когда происходит проектирование API, и попробуем понять, когда и что следует применять.

Также по ходу статьи буду оставлять ссылки на хорошие (по моему мнению) статьи для более глубокого погружения в поднимаемые темы.

API. Каким он должен быть?

Начнём с того, что API (Application Programming Interface) — это программный интерфейс, который позволяет различным программным компонентам взаимодействовать друг с другом. Всё довольно просто: API представляет собой набор методов, которые на вход получают какие-то параметры и выполняют какое-то действие с ними, при этом для реализации такого интерфейса могут быть использованы различные технологии.

В этой статье также мы постараемся разобраться с возможными вариантами реализации API.

1 RPC

Идея RPC (Remote Procedure Call) понятна после расшифровки аббревиатуры. RPC — это концепция, в рамках которой программа на одном сервере (компьютере) может вызвать функцию на другом сервере (компьютере). В каком-то смысле можно сказать, что API и есть RPC, а REST API или gRPC — это как варианты реализации. Поэтому предлагаю здесь не останавливаться и перейти к ещё одной известной реализации удалённого вызова процедур.

2 API на основе HTTP

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

HTTP — это протокол прикладного уровня (самого высокого), который использует протокол TCP (протокол на уровень ниже) как транспорт.

Представьте, что компьютеры — это узлы графа, которые напрямую или косвенно связаны. TCP — это протокол, который обеспечивает надёжную доставку данных от одного компьютера (узла графа) к другому в рамках нашей сети (нашего графа) без потерь. Данные отправляются по кусочкам (пакетам), а отправитель получает сообщение о том, что пакет доставлен успешно или неуспешно и нужна ещё одна попытка. А HTTP даёт нам некоторую структуру сообщений, понятную как для клиента, так и для сервера.

Для того чтобы та или иная функциональность на стороне клиента работала, необходимо, чтобы на стороне клиента (самое частое в браузере) запрос формировался и отправлялся, а сервер его понимал и формировал обратное сообщение с какой-то информацией.

Так, в начале взаимодействия формируется TCP-сессия в качестве транспорта, а потом клиентское приложение отправляет HTTP-запрос на сервер, который обрабатывает его, формирует ответ и передаёт этот ответ обратно клиенту.

Давайте разберём, из чего состоит HTTP-запрос и ответ:

Начнём с HTTP-запроса на сервер. Он состоит из трёх компонентов.

Стартовая строка (Start line)
Обычно выглядит стартовая строка именно так: Метод + URL + HTTP/Версии. Действие, необходимое для выполнения, определяется методом, их существует множество, но реально используются несколько: GET, POST, DELETE, PATCH.

URL — это уникальный адрес какого-либо объекта, над которым мы хотим совершить ту или иную операцию.

Заголовки (Headers)
Это «поля», которые переносят вспомогательную/уточняющую информацию. Например, авторизационные токены, данные браузера, тип устройства. Технически заголовков может не быть вовсе, но почти всегда через них передаются метаданные запроса.

Тело запроса (Body)
Также необязательная часть запроса (например, в ручках с глаголом GET тело не используется). В этой части мы передаём какую-то дополнительную информацию, которую сервер должен обработать при вызове определённого метода, в виде текста при помощи определённого формата. Форматы могут использоваться любые (JSON, BSON, XML, HTML и даже Protocol Buffers).

HTTP-ответ имеет похожую структуру.

Статус ответа (Status)
Это стартовая строка HTTP-ответа. Обычно выглядит следующим образом: Версия протокола + код ответа + пояснение.

Заголовки (Headers)
См. описание компонента «Заголовки» в описании HTTP-запроса.

Тело ответа (Body)
См. описание компонента «Тело запроса» в описании HTTP-запроса.

О форматах

Выше я уже говорил, что при использовании протокола HTTP может быть выбран любой формат представления текста, но самым популярным для API в силу своей читаемости и удобства стал, конечно же, JSON. Сначала он выступил альтернативой, а позже почти полностью вытеснил SOAP с XML-форматом. То есть, когда мы говорим про API, использующий HTTP, обычно подразумевается JSON-over-HTTP API, что в прямом смысле означает, что для API-сервиса мы используем протокол HTTP и JSON как формат для передачи информации в теле запроса. Также я на просторах интернета встречал название HTTP API.

Плюсы и минусы API на основе HTTP  

+ Быстро и легко разрабатываются.

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

- Тяжёлые сообщения из-за текстового формата.

3 REST API

Со временем один из создателей HTTP-протокола Рей Фидинг сформулировал архитектурные принципы, соблюдая которые при проектировании архитектуры можно избежать ряда проблем. Давайте взглянем на эти принципы.

  1. Клиент-серверная архитектура. Это правило говорит о том, что клиентская часть ПО не является самодостаточной и должна обращаться к серверу для решения той или иной задачи.

  2. Хранение сессии на клиенте Stateless. Сервер не должен хранить состояние сессии клиента и с каждым новым запросом должен получать всю необходимую информацию для его выполнения.

  3. Кэширование. Для оптимизации количества запросов какие-то не часто изменяемые данные нужно временно хранить локально у клиента/на сервере.

  4. Единообразие интерфейса. API должен быть универсальными интерфейсом, определяющимся 4 HTTP-глаголами: GET (получение ресурса), POST (создание нового ресурса), PUT (обновление ресурса) и DELETE.

  5. Многослойная архитектура. Зачастую схема взаимодействия компонентов между собой включает в себя множество компонентов (прокси, гейтвеи, микросервисы с разными функциональностями и т. д.). Данная концепция предполагает, что ни клиент, ни сервер не должны быть осведомлены о том, как осуществляется цепочка вызовов за пределами их непосредственных соседей.

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

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

Мысленно вернёмся к факту, что автор этих принципов — один из создателей HTTP, поэтому напрашивается вывод, что протокол создавался, чтобы по максимуму их учитывать. Поэтому неудивительно, что этот архитектурный стиль реализуется именно на HTTP.

По моему мнению, самым важным является принцип единообразия интерфейса, поскольку именно он и говорит нам, что API должен строиться по определённой спецификации, которая будет понятна для всех потенциальных клиентов.
Это главное отличие REST API от просто API поверх протокола HTTP.

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

В связи с этим сегодня довольно популярными суждениями являются следующие.

  1. REST API — это синоним API на основе HTTP. В целом для упрощения коммуникации IT-специалистов можно принять это определение, хотя фактически один API построен согласно архитектурному стилю, а другой — это API, использующий определённый протокол.

  2. Для REST API используется JSON. Выше я уже раскрыл, что JSON — это лишь один из форматов (хоть и самый популярный), которые могут использоваться для представления и передачи текстовой информации по HTTP.

Таким образом, для упрощения предлагается называть «JSON-over-HTTP API, спроектированный с соблюдением архитектурного стиля REST» просто «REST API».

Также API, удовлетворяющие ограничениям REST, называют RESTful API.

Для лёгкости поддержки микросервисов API должны быть понятно и полно задокументированы. Здесь на помощь и приходит Swagger. Этот инструмент позволяет легко генерировать документацию и примеры кода, определяя структуру, конечные точки.

Плюсы и минусы REST API

+ Прост в реализации несмотря на то, что здесь будет чуть сложнее, чем просто с HTTP (придётся соблюдать спецификацию).

+ Легко интегрироваться (спасибо спецификации Open API).

+ Легко читаемый формат JSON.

- Тяжёлые сообщения из-за текстового формата.

Подробнее про REST можно прочитать в замечательной статье.

4 gRPC

Испытав потребность в высокоэффективных системах, компания Google выпустила свой фреймворк (технологию) gRPC, а также под него наши американские коллеги выпустили новую версию транспортного протокола HTTP/2.0. Давайте пройдёмся по особенностям, отличающим этот подход от REST.

Особенность 1. Protocol Buffers

В качестве формата данных вместо JSON используется Protocol Buffers (в народе протобаф). Protobuf — это бинарный формат данных, который подразумевает, что в сообщении вместо массива пар ключ-значение (как это устроено в JSON, где пары ключ-значение можно писать в любом порядке) мы просто отправляем значения в установленном порядке. При этом оба участника сообщения имеют «под рукой» протоконтракт, который и позволяет определить, под каким порядковым номером идёт значение по тому или иному ключу. Другими словами, и клиент, и сервер знают, в каком строгом порядке должны идти значения и, так как это условие реализуется, то и потребности в лишней прогонке ключей по сети нет. А представьте, какая это крутая оптимизация на масштабе в тысячи запросов в секунду. Это плюс!

Но у бинарного формата есть и обратная сторона: формат менее человеко-читаемый, чем текстовый JSON.

 Пример протоконтракта:

message User {
	int32 id = 1;
	string name = 2;
	string email = 3;
 }

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

Бывает, что API gRPC-сервиса представлено в виде набора методов в Swagger. Такое представление делают при помощи gRPC-gateway, конвертируя (поксируя) gRPC в REST для повышения читабельности и упрощения аналитики. Получается, что по факту аналитик может вызывать REST-методы через Swagger с удобным JSON, но сам сервис при этом остается gRPC.  Для интересующихся есть пример в статье.

Особенность 2. HTTP/2.0

В предыдущей актуальной версии протокола — HTTP/1.1 — необходимо было устанавливать несколько TCP-соединений (клиент — сервер), если нужно было сделать несколько запросов. Есть хороший бытовой пример, иллюстрирующий неудобство такого подхода. Если вы хотите задать другу несколько вопросов по телефону, то, получив ответ на вопрос, вы сбрасываете звонок, звоните снова и задаёте новый вопрос, и так по кругу.

Принцип мультиплексирования запроса, реализованный в HTTP/2.0, решает эту проблему. HTTP-запрос делится на два «фрейма». Один (Headers frame) содержит в себе заголовки, а второй (Data frame) — полезные данные. Это позволяет прогонять полезные данные из нескольких запросов с одними и теми же заголовками, причём система будет воспринимать их как один запрос.

В HTTP/2.0 также предусмотрено сжатие заголовков запроса, что еще сильнее облегчает запрос, но в этой статье нам не так важно, как именно это реализуется. Главное — понять, что gRPC эффективнее и быстрее, чем REST.

Благодаря все тому же принципу мультиплексирования существует 4 способа использования gRPC.

Унарный (Unary)

Это самый простой и распространённый паттерн, при котором клиент отправляет один запрос и получает один ответ от сервера. Это похоже на обычный HTTP-запрос и ответ.

Серверный стриминг (Server streaming)

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

Клиентский стриминг (Client streaming)

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

Двунаправленный стриминг (Bidirectional streaming)

При такой реализации клиент и сервер могут отправлять и получать несколько сообщений в обоих направлениях. Сообщения могут отправляться и приниматься независимо, без строгого порядка. Это полезно в ситуациях, когда клиенту и серверу нужно вести непрерывный и динамический диалог или обмениваться данными в режиме реального времени, к примеру, чат в реальном времени или финансовые транзакции с изменением котировок.

Плюсы и минусы gRPC

+ Быстрый. То, что нужно для высоконагруженных систем.

+ Уже написанная спецификация.

+ Есть возможность сделать стриминг информации.

- Не так прост в реализации.

- Менее читаемый Protobuff (в отличие от JSON).

5 Сравнение gRPC, REST API и API на основе HTTP

 

API на основе HTTP

REST API

gRPC

Спецификация

Отсутствует

Есть

Есть

Объём передаваемых данных

Большой, потому что передается текст

Большой, потому что передается текст

Маленький благодаря бинарному формату (не передаются ключи и спецсимволы как в JSON)

Возможность стриминга

Отсутствует

Отсутствует

Есть

Читабельность контрактов/сообщений

Низкая, потому что нет определённого формата

При использовании JSON высокая

Низкая из-за особенностей Protocol Buffers

Когда используется?

Не используется

Подходит для проектов с небольшой нагрузкой, а также используется при написании API для веб-браузера

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

Итого

Хочется сказать, что REST API и gRPC — это просто архитектурные стили, которые регламентируют использование протокола HTTP и форматов сообщений (JSON и Protobuf). У gRPC есть ряд возможностей, которые подходят больше для реализации внутренних высоконагруженных систем, а REST чаще используется для интеграций с фронтендом и сторонними решениями из-за своего подхода к функциям как к операциям над ресурсами и более человеко-читаемого формата.

Да, по факту можно пойти против системы и сделать свой вид API с использованием HTTP-протокола и, допустим, XML. Но зачем, если за нас уже придумали эффективные способы реализации сервисных интерфейсов и расписали всё в спецификациях, описали и накопили большой опыт использования, к которому можно обратиться.

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

Дополнения и комментарии только приветствуются!

Отдельное спасибо хочется передать моему другу и коллеге — Артёму Заборскому, без которого эта статья не получилась бы такой, какая она вышла. А также благодарность коллегам Владимиру Шаплину и Илье Борисюку за ревью.

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


  1. positroid
    21.11.2024 12:53

    Особенность 2. HTTP/2.0

    HTTP/2 работает и для REST API

    Объем передаваемых данных: Большой, потому что передается текст

    Стоит учесть повсеместно используемый gzip и выигрыш protobuf над json на больших (больше 1 Кб) / сложных данных сведётся к 15-30%. Можно составить синтетический пример, в котором структура сообщения состоит из справочника float-чисел, в таком случае выигрыш gRPC будет максимальным.

    Впрочем, это не отменяет всех прочих недостатков и преимуществ, описанных в статье, спасибо)


    1. sokolov_maksim Автор
      21.11.2024 12:53

      Спасибо за комментарий!


    1. gdt
      21.11.2024 12:53

      Тут есть ещё момент с парсингом сообщений, парсинг json далеко не бесплатный.


  1. leon-mbs
    21.11.2024 12:53

    как по мне так оптисальный вариант - jsonRPC


    1. piton_nsk
      21.11.2024 12:53

      Есть опыт работы с ним - плюсы/минусы в реальной жизни, подводные камни, просто некое мнение по опыту работы? Сама идея мне весьма нравится, но вживую не встречал.


  1. gdt
    21.11.2024 12:53

    Я бы не стал приравнивать REST к RPC. REST вроде как больше ориентирован на работу с данными (CRUD операции по сути - создать / получить / обновить / удалить что-то), в то время как RPC - это именно удалённый вызов какой-то логики, любой. REST работает с ресурсами, в то время как RPC - с действиями, которые можно выполнять. Вроде как они даже в каком-то роде противоставляются друг другу - см. например вики - https://ru.wikipedia.org/wiki/REST):

    REST является альтернативой RPC

    Например, gRPC можно использовать для поднятия RPC канала локально, между приложением и сервисом, на одной машине. Чтобы REST для таких целей использовали - ни разу не видел.


    1. sokolov_maksim Автор
      21.11.2024 12:53

      В какой то степени согласен, но те мне менее почему CRUD операции не не могут быть RPC? Ведь вызываются они удаленно, пусть даже REST это про ресурсы, а не про действия.

      В статье я говорю что любое API это RPC (мы удаленно вызываем какие то методы), разве это не так?


      1. gdt
        21.11.2024 12:53

        Смысл в том, что в случае с REST мы не вызываем методы явно, а работаем с ресурсами. Естественно, на каком-то уровне какой-то код своим запросом мы выполним, но в случае REST нам неважно, что делает этот код, до тех пор, пока он выполняет нужные нам операции с ресурсами. Например, это может быть какой-то API сервис заметок - где мы будем создавать заметки, обновлять заметки, получать их и удалять, типа такого:

        1. GET /notes

        2. GET /notes/{id}

        3. PUT /notes/{id} (или POST /notes)

        4. PATCH /notes/{id}

        5. DELETE /notes/{id}

        Т е наш API не даёт нам возможность вызвать какую-то процедуру, давая нам вместо этого возможность работать с ресурсами. В случае с RPC можно было бы сделать какой-то такой API:

        1. POST /getAllNotes

        2. POST /getNoteById

        3. POST /createNote

        4. POST /updateNote

        5. POST /deleteNote

        Т е мы своими вызовами явно выполняем какие-то конкретные действия. Теперь мы можем сделать то, что обычно в REST не делается, например:

        • POST /backupNotes - такой метод мог бы сделать бэкап заметок, и отправить его куда-нибудь

        • POST /emailNote - типа отправить заметку на почту

        • POST /printNote - распечатать заметку

        И так далее. Конечно, для работы с ресурсами (как заметки) - REST подходит намного лучше, даже выглядит как-то красивее и лаконичнее.


  1. AlexeyK77
    21.11.2024 12:53

    Эхх молодежь, а ведь CORBA, DCOM были совсем недавно, и даже не упомянули их, а какие срачеки были по их поводу, особенно когда замаячил SOAP.. (кряхтя), где-то тогда я и отчалил с этой шхуны. ;)

    P.S. За статью пасиба.


    1. sokolov_maksim Автор
      21.11.2024 12:53

      Спасибо за коммент! Думал еще про SOAP написать, но уж слишком много бы вышло, да и кажется, что он не особо то и используется на сегодняшний день уже


    1. pvzh
      21.11.2024 12:53

      Да уж, затащить SOAP или CORBA в новый проект – отличная идея нет, спасибо;) Цель статьи обозначена же в начале. Чего не упомянули, так это JSON-RPC


  1. Kanut
    21.11.2024 12:53

    В каком-то смысле можно сказать, что API и есть RPC, а REST API или gRPC — это как варианты реализации. Поэтому предлагаю здесь не останавливаться и перейти к ещё одной известной реализации удалённого вызова процедур.

    REST по определению должен быть stateless и casheable . RPC совсем не обязательно должен быть stateless, а делать его casheable часто даже просто очень плохая идея.То есть вы в теории можете реализовать какое-то подмножество RPC при помощи REST. Но далеко не все варианты.

    Поэтому прежде чем идти дальше я бы всё-таки предложил вам остановиться и разобраться.


  1. geornit25
    21.11.2024 12:53

    имхо, если вопрос производительности не критичный, то REST с JSON выглядит более предпочтительным. куда проще потом разбирать баги, когда есть человекочитаемые данные в текстовом формате, а не кусок бинарных данных, для которого ещё схему знать надо, чтобы прочитать корректно.


    1. Kanut
      21.11.2024 12:53

      Тот же Wireshark вполне себе позволяет дебажить gRPC; https://grpc.io/blog/wireshark/


      1. onegreyonewhite
        21.11.2024 12:53

        Проблемы тут сразу две:

        1. TLS - gRPC без сертификата в принципе боль. Локальная разработка превращается в цирк с конями на ровном месте.

        2. Remote debugging - не всегда клиент и сервер для дебага можно разместить в одной сети. А если сервис на чём-то безгуёвом и в облаке, то боль умножается на 2. MitM частично решают, но не умеют в gRPC.

        Вот поэтому многие и используют HTTP - на нём банально инструментарий богатый на все случаи жизни и отладки. Не говоря уже о важном факторе: схему надо знать. Без схемы не будет интеграции. Если сервер не подконтрольный, то с ним дружить не получится.


        1. Kanut
          21.11.2024 12:53

          TLS - gRPC без сертификата в принципе боль

          И опять же :

          https://gitlab.com/wireshark/wireshark/-/wikis/How-to-Export-TLS-Master-keys-of-gRPC

          https://github.com/salrashid123/grpc_sslkeylog?tab=readme-ov-file

          Remote debugging - не всегда клиент и сервер для дебага можно разместить в одной сети.

          Я может что-то не так понимаю, но зачем вам иметь их в одной сети чтобы использовать Wireshark?

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

          Я не спорю что это проще. Но это не так что дебажить gRPC вот прямо совсем невозможно.


  1. Helfr
    21.11.2024 12:53

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


  1. funca
    21.11.2024 12:53

    И REST и RPC решают проблему взаимодействия между компонентами в распределенных приложениях. Я бы добавил в этот список ещё Event-driven.

    REST смотрит сюда как на задачу передачи состояния. RPC - намерений совершать какие-то действия. Events, наоборот, уведомляют об уже случившемся. Выбор того или иного стиля зависит от конкретных требований (чаще - нефункциональных). При чем, разные стили можно комбинировать.

    Внутри вы можете использовать любой удобный формат данных: JSON, XML, plain text или тот же protobuf - не суть. Это больше вопрос дизайна, нежели архитектуры.


  1. zzstarling
    21.11.2024 12:53

    Как по мне, так

    • между своими сервисами или стримить - gRPC

    • наружу что-то простое - REST

    • наружу что-то сложное или если надо надо шейпить - GraphQL