В сегодняшнем тексте я хочу более подробно рассмотреть gRPC и REST, возможно, две из самых часто используемых подходов для создания API в наши дни.
I will start with a short characteristic of both tools — what they are and what they can offer. Then I will compare them according to seven categories, in my opinion, most crucial for modern-day systems.
Категории следующие:
- Основные протоколы HTTP
- Поддерживаемые форматы данных
- Размер данных
- Пропускная способность
- Определения
- Легкость внедрения
- Поддержка инструментов
Почему
Когда люди слышат “API”, они, вероятно, сразу же думают о REST API. Однако REST – это всего лишь один из многих подходов к созданию API. Это не универсальное решение для всех случаев использования. Есть и другие способы, причем RPC (Удаленный вызов процедуры) является лишь одним из них, а gRPC, вероятно, является наиболее успешным фреймворком для работы с RPC.
Несмотря на то, что gRPC является довольно зрелым и эффективным технологией, его все еще считают новым. Таким образом, он менее широко принят, чем REST, хотя и очень удобен в некоторых случаях использования.
Моей основной причиной написания этого блога является популяризация gRPC и выявление случаев, когда он может проявить себя.
Что такое REST?
REST, или Representational State Transfer, вероятно, является наиболее распространенным способом создания приложения, которое предоставляет любую API. Он использует HTTP в качестве базового средства связи. Благодаря этому, он может извлекать выгоду из всех преимуществ HTTP, таких как кэширование.
Более того, будучи по своей природе без состояния, REST позволяет легко отделить клиента от сервера. Клиент должен знать только интерфейс, предоставляемый сервером, чтобы эффективно с ним взаимодействовать, и не зависит ни в какой мере от реализации сервера. Связь между клиентом и сервером основана на принципе запроса и ответа, при этом каждый запрос является классическим HTTP-запросом.
REST не является протоколом или инструментом (в определенной степени): это архитектурный подход к созданию приложений. Службы, которые следуют подходу REST, называются RESTFul-службами. Как архитектура, он налагает несколько ограничений на своих пользователей. В частности:
- Клиент-серверная связь
- Безсостояние связь
- Кэширование
- Единый интерфейс
- Иерархическая система
- Код по запросу
Два ключевых понятия REST:
- Конечные точки: Уникальный URL (Uniform Resource Locator), представляющий конкретный ресурс; можно рассматривать как способ доступа к определенной операции или элементу данных через Интернет
- Ресурс: Определенная часть данных, доступная под конкретным URL
Более того, существует описание под названием Модель созревания Ричардсона — модель, описывающая степень “профессионализма” в REST API. Она делит REST API на 3 уровня (или 4, в зависимости от того, считаете ли вы уровень 0) на основе набора характеристик, которыми обладает конкретное API.
Одна из таких характеристик заключается в том, что конечная точка REST должна использовать существительные в URL и правильные методы HTTP для управления своими ресурсами.
- Пример: DELETE user/1 вместо GET user/deleteById/1
Что касается методов HTTP и связанных с ними действий, то это выглядит следующим образом:
- GET — Получить конкретный ресурс или коллекцию ресурсов
- POST — Создать новый ресурс
- PUT — Обновить весь ресурс
- PATCH — Частичное обновление конкретного ресурса
- DELETE — Удалить конкретный ресурс по идентификатору
Модель созревания определяет намного больше; например, концепцию под названием ГиперМедиа. ГиперМедиа связывает представление данных и управление действиями, которые клиенты могут выполнять.
A full description of the maturity model is out of the scope of this blog — you can read more about it here.
Внимание: многие упомянутые в этом абзаце вещи более тонки, чем здесь описано. REST — это довольно обширная тема и заслуживает целой серии статей. Тем не менее, все здесь соответствует всем общеизвестным лучшим практикам REST.
Что такое gRPC?
Это еще одна реализация относительно старого понятия удаленного вызова процедур (Remote Procedure Call). Она создана специалистами из Google, поэтому в ее названии присутствует буква “g”. Вероятно, это самый современный и эффективный инструмент для работы с RPC, а также проект инкубации CNCF.
gRPC использует Protocol Buffers от Google в качестве формата сериализации данных, а также HTTP/2 в качестве транспортного медиума, хотя gRPC также может работать с JSON на уровне данных.
Основные строительные блоки gRPC включают:
- Метод: Базовый строительный блок gRPC, каждый метод представляет собой удаленный вызов процедуры, который принимает некоторые входные данные и возвращает выходные. Он выполняет одну операцию, реализованную далее на выбранном программистском языке. На данный момент gRPC поддерживает 4 типа методов:
- Универсальный (Unary): Классическая модель запрос-ответ, где метод принимает входные данные и возвращает выходные
- Потоковая передача со стороны сервера (Server Streaming): Методы принимают сообщение в качестве входных данных, а возвращают поток сообщений в качестве выходных. gRPC гарантирует порядок сообщений в рамках отдельного вызова RPC.
- Потоковая передача со стороны клиента (Client Streaming): Метод принимает поток сообщений в качестве входных данных, обрабатывает их до тех пор, пока сообщения не закончатся, и затем возвращает одно сообщение в качестве выходных данных. Аналогично предыдущему, gRPC гарантирует порядок сообщений в рамках отдельного вызова RPC.
- Двунаправленное потоковое: Метод принимает поток в качестве входных данных и возвращает поток в качестве выходных данных, эффективно используя два потока чтения и записи. Оба потока работают независимо, и сохраняется порядок сообщений на уровне потока.
- Сервис: Представляет собой группу методов — каждый метод должен иметь уникальное имя в пределах сервиса. Сервисы также описывают такие функции, как безопасность, тайм-ауты или повторы.
- Сообщение: Объект, представляющий входные или выходные данные методов.
Определения gRPC API написаны в виде файлов .proto, которые содержат все три основных строительных блока, упомянутых выше. Кроме того, gRPC предоставляет компилятор протобуфера, который генерирует клиентский и серверный код из наших файлов .proto.
Мы можем реализовать серверные методы так, как захотим. Нам нужно придерживаться контракта вход-выход API.
На стороне клиента есть объект под названием клиент (или stub) — подобно клиенту HTTP. Он знает все методы сервера и просто обрабатывает вызов удаленных процедур и возвращение их ответов.
Сравнение
Основной HTTP Протокол
Это первый и, возможно, самый важный раздел, так как его влияние может быть заметно и в других.
В общем, REST основан на запросе-ответе и использует HTTP/1.1 в качестве транспортного средства. Нам нужно использовать другой протокол, такой как WebSocket (подробнее о них здесь) или любой тип потокового или более долговременного соединения.
Мы также можем реализовать хакерский код, чтобы сделать REST похожим на потоковое. Более того, использование HTTP/1.1 для REST требует одного соединения на каждую обменную операцию запрос-ответ. Такой подход может быть проблематичным для долго выполняющихся запросов или когда у нас ограниченные возможности сети.
Конечно, мы можем использовать HTTP/2 для создания REST-подобных API; однако не все серверы и библиотеки могут поддерживать HTTP/2 пока. Таким образом, проблемы могут возникнуть в других местах.
С другой стороны, gRPC использует только HTTP/2. Это позволяет отправлять несколько пар запрос-ответ через одно TCP-соединение. Такой подход может быть довольно значительным повышением производительности для нашего приложения.
- Результат: Небольшая победа для gRPC
Поддерживаемые форматы данных
Предполагая, что по умолчанию REST API использует HTTP/1.1, тогда он может поддерживать многие форматы.
REST, как правило, не налагает никаких ограничений на формат сообщения и стиль. По сути, каждый формат, который можно сериализовать в обычный текст, является действительным. Мы можем использовать любой формат, который подходит нам лучше в определенной ситуации.
Наиболее популярным форматом для отправки данных в REST-приложениях, безусловно, является JSON. XML занимает второе место из-за большого количества старых/устаревших приложений.
Однако, используя REST с HTTP/2, поддерживаются только двоичные форматы обмена. В этом случае мы можем использовать Protobuf или Avro. Конечно, такой подход может иметь свои недостатки, но об этом подробнее в следующих пунктах.
В то же время, gRPC поддерживает только два формата для обмена данными:
- Protobuf — По умолчанию
- JSON — Когда вам нужно интегрироваться с более старой API
Если вы решите попробовать с JSON, то gRPC будет использовать JSON в качестве формата кодирования сообщений и GSON в качестве формата сообщений. Кроме того, использование JSON потребует выполнения дополнительной конфигурации. Вот документация gRPC о том, как это сделать.
- Результат: Победа для REST, так как он поддерживает больше форматов.
Размер данных
По умолчанию, gRPC использует бинарный формат обмена данными, который ЗНАЧИТЕЛЬНО уменьшает размер сообщений, передаваемых по сети: исследование говорит о примерно 40–50% меньшем размере в байтах — мой опыт из одного из предыдущих проектов говорит даже о 50–70% меньшем размере.
В статье выше представлено относительно глубокое сравнение размеров между JSON и Protobuff. Автор также предоставил инструмент для генерации JSON и бинарных файлов. Таким образом, вы можете повторить его эксперименты и сравнить результаты.
Объекты из статьи довольно просты. Тем не менее, общее правило таково — чем больше вложенных объектов и чем сложнее структура JSON, тем он будет тяжелее по сравнению с Protobuf. Разница в размере в 50% в пользу Protobuf является хорошей отправной точкой.
Разница может быть сведена к минимуму или устранена при использовании бинарного обменного формата для REST. Тем не менее, это не является наиболее распространенным или лучше всего поддерживаемым способом реализации RESTful API, поэтому могут возникнуть другие проблемы.
- Результат: В стандартном случае, победа gRPC; в случае использования бинарного формата данных, ничья.
Пропускная способность
Опять же, в случае REST все зависит от базового протокола HTTP и сервера.
В стандартном случае, REST на основе HTTP/1.1, даже самый производительный сервер не сможет превзойти производительность gRPC, особенно когда мы добавляем накладные расходы на сериализацию и десериализацию при использовании JSON. Хотя при переходе на HTTP/2 разница кажется уменьшается.
Что касается максимальной пропускной способности, в обоих случаях HTTP является средством транспортировки, поэтому он имеет потенциал масштабирования до бесконечности. Таким образом, все зависит от инструментов, которыми мы пользуемся, и от того, что именно мы делаем с нашим приложением, так как по своей сути нет ограничений.
- Результат: В стандартном случае, gRPC; в случае использования бинарных данных и HTTP/2, ничья или небольшая победа gRPC.
Определения
В этой части я опишу, как мы определяем наши сообщения и сервис в обоих подходах.
В большинстве приложений REST мы просто объявляем наши запросы и ответы как классы, объекты или любую другую структуру, поддерживаемую конкретным языком. Затем мы полагаемся на предоставленные библиотеки для сериализации и десериализации JSON/XML/YAML или любого другого формата, который нам нужен.
Более того, ведутся постоянные усилия по созданию инструментов, способных генерировать код на выбранном языке программирования на основе определений REST API из Swagger. Однако они, похоже, находятся в альфа-версии, поэтому могут возникать некоторые ошибки и мелкие проблемы, которые сделают их трудными в использовании.
Различия между двоичными и недвоичными форматами для приложений REST минимальны, так как правила более или менее одинаковы в обоих случаях. Для двоичного формата мы просто определяем все так, как требует конкретный формат.
Кроме того, мы определили наш REST-сервис через методы или аннотации из нашей базовой библиотеки или фреймворка. Инструмент также отвечает за предоставление его наряду с другими конфигурациями внешнему миру.
В случае gRPC у нас есть Protobuf по умолчанию и де-факто единственный способ написания определений. Мы должны объявить все: сообщения, сервисы и методы в .proto файлах, так что дело довольно прямолинейно.
Затем мы используем инструмент, предоставленный gRPC, для генерации кода для нас, и нам просто нужно реализовать наши методы. После этого все должно работать, как задумано.
Более того, Protobuf поддерживает импорт, поэтому мы можем распределить нашу настройку по нескольким файлам довольно просто.
- Результат: Здесь нет победителя, просто описание и совет от меня: выбирайте тот подход, который больше всего подходит вам.
Легкость адаптации
В этом разделе я сравниваю поддержку библиотек/фреймворков для каждого подхода в современных языках программирования.
Вообще говоря, каждый язык программирования (Java, Scala, Python), с которым я столкнулся на начальном этапе своей карьеры в качестве инженера по программному обеспечению, имеет по крайней мере 3 основных библиотеки/фреймворка для создания приложений в стиле REST, не говоря уже о схожем количестве библиотек для парсинга JSON в объекты/классы.
Кроме того, поскольку REST по умолчанию использует человекочитаемые форматы, его проще отлаживать и использовать для новичков. Это также может повлиять на скорость доставки новых функций и помочь бороться с ошибками, возникающими в вашем коде.
Короче говоря, поддержка приложений в стиле REST, как минимум, очень хорошая.
В Scala у нас даже есть инструмент под названием tapir — которым я имел удовольствие быть одним из сопровождающих в течение некоторого времени. Tapir позволяет абстрагировать наш HTTP-сервер и писать конечные точки, которые будут работать на нескольких серверах.
Сам gRPC предоставляет клиентскую библиотеку для более чем 8 популярных языков программирования. Обычно этого достаточно, так как эти библиотеки содержат все необходимое для создания API gRPC. Кроме того, я знаю библиотеки, предоставляющие более высокие абстракции для Java (через Spring Boot Starter) и для Scala.
Еще один момент заключается в том, что сегодня REST считается всемирным стандартом и точкой входа для создания сервисов, в то время как RPC и gRPC, в частности, по-прежнему рассматриваются как новинка, несмотря на то что они уже довольно стары.
- Результат: REST, поскольку он более широко принят и имеет гораздо больше библиотек и фреймворков вокруг
Поддержка инструментов
Библиотеки, фреймворки и общие рыночные доли были рассмотрены выше, поэтому в этой части я хотел бы рассмотреть инструменты, связанные с обоими стилями. Это означает инструменты для тестов, тестов производительности/нагрузки и документации.
Автоматические тесты/Тесты
Во-первых, в случае REST, инструменты для создания автоматических тестов встроены в разные библиотеки и фреймворки или являются отдельными инструментами, созданными с этой единственной целью, например, REST-assured.
В случае gRPC мы можем сгенерировать заглушку и использовать ее для тестов. Если мы хотим быть еще более строгими, мы можем использовать сгенерированный клиент в качестве отдельного приложения и использовать его в качестве основы для наших тестов на реальном сервисе.
Что касается поддержки внешних инструментов для gRPC, я знаю:
- Приложение Postman поддержка gRPC
- Клиент HTTP JetBrains, используемый в их IDE, также может поддерживать gRPC с некоторой минимальной настройкой
- Результат один: Победа для REST; однако ситуация, кажется, улучшается для gRPC.
Тесты производительности
Здесь REST имеет значительные преимущества, так как инструменты вроде JMeter или Gatling делают нагрузочное тестирование API REST относительно простым делом.
К сожалению, gRPC не имеет такой поддержки. Я знаю, что люди из Gatling включили плагин gRPC в текущей версии Gatling, так что ситуация, кажется, улучшается.
Однако до сих пор у нас был только один неофициальный плагин и библиотека под названием ghz. Все это хорошо; просто это не на том же уровне поддержки, как для REST.
- Результат два: Победа для REST; однако ситуация, кажется, улучшается для gRPC, опять же 😉
Документация
В случае документации API, победа снова за REST с OpenAPI и Swagger, которые широко приняты в индустрии и являются фактическим стандартом. Почти все библиотеки для REST могут предоставлять документацию swagger с минимальными усилиями или прямо из коробки.
К сожалению, gRPC не имеет ничего подобного.
Однако вопрос заключается в том, нужна ли вообще gRPC такая инструментарий. gRPC по своей природе более описательный, чем REST, поэтому дополнительные инструменты документирования могут.
В общем, .proto файлы с описанием нашего API более декларативны и компактны, чем код, отвечающий за создание нашего кода REST API, так что, возможно, из gRPC не требуется больше документации. Ответ я оставляю за вами.
- Результат три: Победа для REST; однако вопрос о документации gRPC остаётся открытым.
Общее Результат:
A significant victory for REST
Итог
Финальная таблица результатов выглядит следующим образом.

Очки распределены поровну между обоими стилями, с тремя победами каждый и одной категорией без явного победителя.
Серебряной пули нет: просто подумайте, какие категории могут быть наиболее важными для вашего приложения, а затем выберите подход, который выиграл в большинстве из них — по крайней мере, это мой совет.
Что касается моего предпочтения, я бы попробовал gRPC, если бы мог, так как он работал довольно хорошо в моём последнем проекте. Возможно, это будет лучшим выбором, чем обычный старый REST.
Если вам когда-нибудь нужно будет выбрать между REST и gRPC или решить любую другую техническую проблему, просто дайте мне знать. Возможно, я смогу помочь.
Спасибо за ваше время.