коротко

REST — это стиль, где система — набор ресурсов с адресами, а действия задаются HTTP-методами; данные обычно в JSON, который читается глазами. gRPC — это RPC (Remote Procedure Call): вы вызываете удалённую функцию так, будто она локальная — «посчитай», «начисли», «проверь». Контракт задаётся строгой схемой в файле .proto, данные едут в бинарном формате Protocol Buffers поверх HTTP/2, по нему же из схемы генерируется код клиента и сервера. gRPC быстрее и компактнее REST и умеет стриминг, но бинарь не прочитать глазами, из браузера напрямую он почти не работает и отлаживать его сложнее. Грубое правило: gRPC — для внутренних сервисов под нагрузкой, REST — для публичного API и браузера.

Однажды я полдня доказывал коллеге, что интеграция «не отвечает». Открываю инструмент, шлю запрос — в ответ нечитаемая каша из байтов, ни одного знакомого поля. Я привык, что API — это JSON, который можно прочитать как письмо. А это был gRPC: бинарный формат, который без схемы .proto вообще не разворачивается в человеческий вид. Сервис отвечал прекрасно — просто я смотрел на него не теми глазами. С этого началось моё понимание, что REST и gRPC — это два разных способа думать о вызове, а не «обычный API» и «какой-то странный». Если совсем коротко: REST читают люди, gRPC читают машины — выбираешь не протокол, а аудиторию вызова.

REST: ресурсы, которые читаются глазами

REST мы подробно разбирали в записи про REST API, поэтому здесь коротко — как точку отсчёта. Главная идея REST: всё, чем оперирует система, — это ресурсы (заказы, пользователи), у каждого есть адрес-URL, а действие задаётся HTTP-методом: GET — прочитать, POST — создать, и так далее. Данные обычно едут в JSON — текстовом формате, который можно открыть и прочитать как обычный текст.

Вы мыслите существительными: «заказ номер 123», «список пользователей». Вы оперируете вещами и применяете к ним стандартные действия. Это и есть «ресурсный» взгляд на мир.

gRPC: вызов удалённой функции как локальной

gRPC мыслит иначе — глаголами. В основе лежит идея RPC (Remote Procedure Call) — «вызов удалённой процедуры». Звучит сложно, а суть простая: вы вызываете функцию, которая живёт на другом сервере, так, будто она лежит у вас под рукой.

Представьте, что у вас в коде есть функция начислитьБонусы(заказ). Обычно она тут же, в вашей программе. RPC говорит: пусть эта функция физически живёт на чужом сервере, но вы вызываете её той же строчкой, как локальную. Всё, что между вашим вызовом и чужим сервером — сеть, упаковка данных, отправка, распаковка ответа — спрятано под капот. Вы не думаете про URL и HTTP-методы, вы думаете «вызвать метод НачислитьБонусы и получить результат».

То есть REST мыслит ресурсами («вот заказ, измени его»), а gRPC — действиями («выполни вот эту операцию»). Это разные способы описать одно и то же взаимодействие, и они тянут за собой разные технические решения.

«g» в gRPC — это не «Google»

Хотя gRPC сделали в Google, официально буква «g» расшифровывается каждый релиз по-разному (шутка авторов) и к названию компании прямого отношения не имеет. Запоминать это не нужно, но если кто-то уверенно скажет «g — это Google» — это распространённое заблуждение. Важна суть: gRPC — это конкретная реализация идеи RPC от Google.

Protocol Buffers и файл .proto: контракт-схема

Чтобы клиент и сервер договорились, какие функции есть и какие у них аргументы, gRPC использует строгую схему — файл с расширением .proto, написанный на языке Protocol Buffers (protobuf). Это и есть контракт: что можно вызвать, что прислать и что вернётся. Аналог OpenAPI в мире REST, только обязательный и строже.

Выглядит .proto-файл так (это обычный текст, не код для исполнения):

syntax = "proto3";

service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
  rpc CreateOrder (CreateOrderRequest) returns (Order);
}

message GetOrderRequest {
  int64 id = 1;
}

message Order {
  int64 id = 1;
  string status = 2;
  int64 amount = 3;
}

Разберём по строкам. service OrderService — это набор удалённых функций, которые можно вызвать. Каждая строка rpc — одна функция: например, GetOrder принимает GetOrderRequest и возвращает Order. Ниже message — это структуры данных: GetOrderRequest содержит одно поле id, а Order — три поля. Цифры после знака равенства (1, 2, 3) — это номера полей: именно по номеру, а не по имени, поле кодируется в бинарь. Поэтому номер нельзя менять у живого контракта — это сразу ломающее изменение, про которое отдельная запись о версионировании API.

Из этого .proto-файла специальный инструмент генерирует код — готовый клиент и заготовку сервера на нужном языке (Java, Go, Python и т.д.). Разработчику не надо вручную писать упаковку и распаковку данных: схема одна, код из неё сгенерирован автоматически на обеих сторонах. Это сильная сторона gRPC: контракт и код всегда совпадают, потому что код сделан из контракта.

Бинарный формат и HTTP/2: почему быстрее

Два технических отличия дают gRPC скорость. Первое — данные едут не текстом, а в бинарном виде. JSON в REST — это человекочитаемый текст: имена полей, кавычки, скобки занимают место и требуют разбора. Protobuf кодирует то же самое набором байтов по номерам полей — компактнее и быстрее упаковывается-распаковывается.

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

Обратная сторона этих же свойств — бинарь не прочитать глазами в логах и инструментах, а из браузера gRPC напрямую почти не работает: браузеру нужен отдельный прокси-слой (gRPC-Web). Поэтому то, что делает gRPC быстрым внутри, делает его неудобным снаружи.

Как выглядит вызов gRPC

sequenceDiagram
  participant C as Клиент (сгенерён из .proto)
  participant S as Сервер (gRPC)
  C->>C: вызов метода GetOrder(id=123) как локального
  C->>S: бинарный запрос по HTTP/2
  S->>S: распаковал protobuf, выполнил функцию
  S-->>C: бинарный ответ Order{id, status, amount}
  C->>C: распаковал в обычный объект

Схема показывает путь одного вызова. Разработчик в своём коде вызывает GetOrder(id=123) — для него это обычная локальная функция, сгенерированная из .proto. Под капотом сгенерированный клиент упаковывает аргументы в бинарный protobuf и отправляет серверу по HTTP/2. Сервер распаковывает запрос, выполняет настоящую функцию, упаковывает результат Order обратно в бинарь и шлёт ответ. Клиент распаковывает его в обычный объект и отдаёт коду как результат вызова. Главное, что нужно вынести: вся упаковка, отправка по сети и распаковка спрятаны — для пишущего код это выглядит как вызов локальной функции, в этом и весь смысл RPC.

REST против gRPC: таблица

СвойствоRESTgRPC
МодельРесурсы (существительные)Вызов функций (глаголы)
Формат данныхJSON (текст, читается глазами)Protobuf (бинарь, не читается)
КонтрактOpenAPI (желателен).proto (обязателен, строгий)
ТранспортHTTP/1.1 или HTTP/2HTTP/2
СтримингСлабо, обходными путямиДа, из коробки
Из браузераНапрямуюТолько через прокси (gRPC-Web)
Скорость и размерМедленнее, объёмнееБыстрее, компактнее
ОтладкаПросто (curl, глазами)Сложнее (нужна схема и инструменты)

Когда что выбирать

gRPC — для общения сервисов между собой внутри системы, особенно когда их много и нагрузка высокая. Это типичная история микросервисной архитектуры: десятки сервисов гоняют запросы друг к другу, важны скорость, компактность и строгий контракт, который не даст разъехаться. Глазами эти вызовы всё равно никто не смотрит — их видят только машины.

REST — для всего, что выходит наружу: публичный API для партнёров, бэкенд для веб-приложения в браузере, интеграции, где на той стороне неизвестно кто. Здесь важнее простота, читаемость и то, что REST понимают вообще все инструменты и разработчики. Платить скоростью за это почти всегда не жалко. А если у клиента много разных экранов и каждому нужен свой набор полей — есть третий подход, GraphQL, где форму ответа задаёт сам клиент.

Простое правило выбора

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

Где это касается аналитика

Если интеграция на gRPC, контракт — это .proto-файл, и читать его надо уметь: какие методы есть, какие сообщения, какие номера полей. Менять контракт нельзя как попало — переименовать поле или переиспользовать номер ломает обе стороны. Если в требованиях на gRPC-сервис вы не зафиксировали методы и сообщения — спека дырявая ровно так же, как REST-спека без описания эндпоинтов.

Что безопасно менять в живом .proto

Правило «не меняй номер поля» — это только вершина. На приёмке изменения контракта аналитику полезен мини-чеклист: что можно делать с живым .proto, не сломав уже работающие клиенты. Логика тут одна — старый сгенерированный код читает байты по номерам полей, поэтому всё крутится вокруг номеров.

ИзменениеБезопасно?Почему
Добавить новое поле с новым номеромДаСтарый клиент не знает про поле и просто его игнорирует; новый — читает.
Добавить новый rpc-методДаСтарые клиенты его не вызывают, новые могут.
Удалить полеС оговоркойПоле убрать можно, но его номер надо пометить как reserved, чтобы случайно не переиспользовать.
Поменять номер существующего поляНетСтарый клиент по старому номеру прочитает мусор.
Переиспользовать номер удалённого поля под другое полеНетСтарый клиент решит, что это прежнее поле, и распакует чужие байты в свой тип.
Поменять тип поля (например int64string)НетБайты на проводе закодированы под старый тип; новый код их не разберёт.
Переименовать поле (имя то же, номер тот же)Да на проводеВ бинарь едет номер, не имя; но сгенерированный код у клиентов перестанет компилироваться, пока не перегенерят — это всё равно повод предупредить.

Помеченный номер выглядит в .proto так: reserved 3; внутри message — это запрет переиспользовать номер 3 под что-то другое. По сути это та же дисциплина, что и при версионировании API, только формализованная самим форматом: добавлять — можно, ломать форму прошлого — нельзя.

Цена gRPC: байты дешевле, эксплуатация дороже

Честный trade-off, который любят умалчивать: gRPC экономит байты и миллисекунды, но платит за это операционной сложностью. Одно долгоживущее HTTP/2-соединение нельзя балансировать обычным L4-балансировщиком (он раскидывает соединения, а не запросы внутри них) — нужен gRPC-aware балансировщик уровня L7, иначе вся нагрузка ляжет на один инстанс. Дебажить в проде сложнее: бинарь не глянешь глазами в логах, нужен grpcurl и сама схема. Тулинг беднее, чем у REST, который понимает любой curl и любой браузер. Для DevOps это реальная цена, и закладывать её надо до выбора, а не после.

Откуда это взялось

Сама идея RPC старая — «вызывать удалённую функцию как локальную» обсуждали ещё в начале 1980-х, а в 1990-х были CORBA и SOAP, тяжёлые и церемонные. Protocol Buffers Google сделал для себя около 2008 года, чтобы компактно и быстро гонять данные между своими сервисами. Сам gRPC Google выложил в открытый код в 2015 году — это была публичная версия внутреннего инструмента под названием Stubby, которым в компании уже годами связывали сервисы. gRPC взлетел вместе с микросервисами и Kubernetes: когда сервисов становятся сотни, цена каждого лишнего байта и каждой миллисекунды складывается в реальные деньги.

Как спрашивают на собесе

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

Когда gRPC, а когда REST?

gRPC — для общения сервисов внутри системы под нагрузкой, где важны скорость, компактность и строгий контракт (типичный микросервисный сценарий). REST — для всего наружу: публичный API, браузер, интеграции с внешними людьми, где ценнее читаемость и универсальность. Сомневаешься — начинай с REST, на gRPC переходят осознанно, когда упираются в скорость или в дисциплину контрактов.

Почему gRPC не работает напрямую из браузера?

Браузер не даёт коду полный контроль над HTTP/2-фреймами и трейлерами, на которых держится gRPC, поэтому «голый» gRPC из вкладки не вызвать. Нужен промежуточный слой gRPC-Web и прокси, который переводит браузерный запрос в настоящий gRPC. Именно поэтому для фронтенда обычно оставляют REST или GraphQL, а gRPC прячут за бэкендом.

Что будет, если поменять номер поля в .proto?

Это ломающее изменение. В бинарь поле кодируется по номеру, а не по имени, поэтому старый клиент по старому номеру прочитает не то поле — распакует чужие байты в свой тип и получит мусор или ошибку. Добавлять поля с новыми номерами безопасно, а удалённый номер надо пометить reserved, чтобы его случайно не переиспользовали.

Чем gRPC-ошибки отличаются от HTTP-кодов?

В gRPC своего HTTP-кода нет — у него свой набор статусов (OK, NOT_FOUND, INVALID_ARGUMENT, DEADLINE_EXCEEDED, UNAVAILABLE и ещё около десятка), общий для всех языков и приезжающий отдельным полем. Маппинг на HTTP не один-к-одному, поэтому в спеке gRPC-метода пишут не «вернёт 404», а «вернёт NOT_FOUND». Отдельно есть deadline: клиент задаёт, сколько ждать, и при просрочке вызов падает с DEADLINE_EXCEEDED.

Частые вопросы

Что такое gRPC простыми словами?

gRPC — это способ одной программе вызвать функцию, которая физически живёт на другом сервере, так, будто она локальная: «выполни вот эту операцию и верни результат». В основе лежит идея RPC (вызов удалённой процедуры). Контракт описывается строгой схемой в файле .proto, данные едут в компактном бинарном формате Protocol Buffers поверх HTTP/2, а код клиента и сервера генерируется из схемы автоматически. В отличие от REST, который мыслит ресурсами, gRPC мыслит вызовами функций.

gRPC или REST — что выбрать?

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

Что такое protobuf?

Protocol Buffers (protobuf) — это язык описания строгой схемы данных и компактный бинарный формат для их передачи, на котором работает gRPC. В файле .proto описывают, какие есть удалённые функции (service и rpc) и какие структуры данных они принимают и возвращают (message с пронумерованными полями). Из этого файла автоматически генерируется код клиента и сервера, поэтому контракт и реализация всегда совпадают. Бинарный формат компактнее и быстрее JSON, но не читается глазами без схемы.