← Все статьи

gRPC, GraphQL и REST: сравнение для аналитика

· 10 мин чтения

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

Когда команда проектирует новую интеграцию, вопрос «REST или что-то ещё» возникает почти всегда. Чаще всего побеждает REST — потому что все его знают. Иногда кто-то из разработчиков предлагает gRPC «потому что быстрее» или GraphQL «потому что фронтенду удобнее». Чтобы такой выбор был осознанным, аналитику нужно понимать, чем эти три стиля отличаются не на уровне лозунгов, а на уровне контракта, поведения в сети и стоимости сопровождения. Разберём их по тем вопросам, которые реально встают при постановке задачи.

Коротко о каждом стиле

REST

REST — архитектурный стиль поверх HTTP: данные представлены как ресурсы с адресами (/orders/42), действия выражаются методами HTTP (GET, POST, PUT, PATCH, DELETE), результат — кодами ответа (200, 201, 404, 409). Формат тела обычно JSON. Контракт описывается в OpenAPI. Строго говоря, большинство «REST API» в индустрии — это JSON поверх HTTP с ресурсными адресами, без гипермедиа из исходной диссертации Филдинга, но для практики это неважно.

gRPC

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

syntax = "proto3";

service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
  rpc WatchOrderStatus (GetOrderRequest) returns (stream OrderStatus);
}

message GetOrderRequest {
  string order_id = 1;
}

message Order {
  string id = 1;
  string customer_id = 2;
  repeated OrderItem items = 3;
  int64 total_kopecks = 4;
}

GraphQL

GraphQL — язык запросов к API, созданный в Facebook. Сервер публикует схему (SDL) — граф типов и связей, а клиент в каждом запросе сам указывает, какие поля и связанные объекты ему нужны. Обычно у API один адрес (/graphql), запросы на чтение называются query, на изменение — mutation, на подписку на события — subscription.

query {
  order(id: "42") {
    id
    status
    customer { name phone }
    items { product { title } quantity }
  }
}

Ответ повторяет форму запроса: ровно эти поля и ничего лишнего.

Сравнение по существу

КритерийRESTgRPCGraphQL
Модельресурсы и методы HTTPпроцедуры (методы сервиса)граф типов, запрос формирует клиент
КонтрактOpenAPI (необязателен, но желателен).proto (обязателен, без него нет кода)схема SDL (обязательна, сервер её публикует)
Формат данныхJSON (текст)Protocol Buffers (бинарный)JSON (текст)
ТранспортHTTP/1.1 или HTTP/2HTTP/2обычно HTTP, подписки — WebSocket или SSE
Типизацияпо схеме OpenAPI, проверка — по желаниюстрогая, проверяется компиляторомстрогая, проверяется сервером на каждый запрос
Потоковая передачанет (только обходные пути: SSE, long polling)встроена, в обе стороныподписки
Кэшированиестандартное HTTP-кэширование, CDNнет стандартногосложно: один адрес, POST-запросы
Ошибкикоды HTTP + тело ошибкистатусы gRPC (NOT_FOUND, UNAVAILABLE...) + деталичаще всего HTTP 200 и массив errors в теле
Вызов из браузеранапрямуютолько через прокси (gRPC-Web)напрямую
Отладкаcurl, Postman, браузернужны grpcurl, Postman с поддержкой gRPC и т.п.GraphiQL, Postman
Порог входанизкийсреднийсредний на клиенте, высокий на сервере

Производительность: где разница реальна

Самый частый аргумент за gRPC — скорость. Он справедлив, но с оговорками.

  • Размер сообщений. Protobuf не передаёт имена полей, только их номера, и кодирует числа компактно. На типичных структурах сообщение меньше JSON в разы. Для мобильных клиентов на плохой связи и для потоков телеметрии это заметно.
  • Сериализация. Разбор бинарного формата дешевле разбора текста. При тысячах вызовов в секунду между внутренними сервисами экономия процессора ощутима.
  • Соединения. HTTP/2 позволяет мультиплексировать много вызовов в одном соединении. Но HTTP/2 доступен и для REST — это не эксклюзив gRPC.

При этом в большинстве бизнес-систем время ответа определяется не форматом, а базой данных, внешними вызовами и логикой. Если сервис отвечает за 200 мс, из которых 190 — запрос в БД, переход с JSON на Protobuf ничего не изменит. Аргумент «быстрее» имеет смысл только там, где узкое место действительно в сети и сериализации: высоконагруженные внутренние вызовы, потоки событий, мобильные клиенты.

У GraphQL своя история производительности. Он экономит сетевые обращения клиента: вместо трёх REST-вызовов (заказ, клиент, товары) — один запрос. Но на сервере этот запрос может развернуться в десятки обращений к базе — классическая проблема N+1, которую решают пакетной загрузкой (DataLoader). Кроме того, клиент может сформировать очень тяжёлый запрос с глубокой вложенностью, и серверу нужны ограничения сложности запросов, иначе это готовый вектор для отказа в обслуживании.

Контракт и его эволюция

Для аналитика ключевой вопрос — как описывается интерфейс и как он меняется со временем, не ломая потребителей.

REST и OpenAPI

Контракт в REST необязателен технически: можно выпустить API вообще без спецификации. Поэтому дисциплину приходится обеспечивать процессом — подходом contract-first, линтерами, проверкой обратной совместимости в CI (подробнее — в статье о подходах к API-контрактам). Версионирование обычно явное: /v1/, /v2/ в адресе или версия в заголовке. Добавлять необязательные поля в ответ безопасно, удалять и переименовывать — нет.

gRPC и Protocol Buffers

В Protobuf совместимость встроена в сам формат, если соблюдать правила:

  • поля идентифицируются номерами, а не именами, поэтому переименование поля не ломает передачу данных (но ломает сгенерированный код клиентов при обновлении);
  • новое поле с новым номером старые клиенты просто проигнорируют;
  • номер удалённого поля нельзя использовать повторно — его помечают reserved;
  • менять тип поля и номер существующего поля нельзя.

Отсюда практическое правило для постановки: в спецификации gRPC-интеграции номера полей — часть контракта, их фиксирует аналитик или архитектор, а не тот, кто первым написал код.

GraphQL и схема

GraphQL традиционно обходится без версий: схема развивается непрерывно. Новые поля и типы добавляются свободно — старые клиенты их не запрашивают и не замечают. Устаревшие поля помечают директивой @deprecated(reason: "..."), отслеживают по логам, кто их ещё запрашивает, и удаляют, когда потребителей не осталось. Это удобно, но требует наблюдаемости: нужно знать, какие поля какие клиенты используют.

Обработка ошибок

Здесь три стиля различаются сильнее, чем кажется, и это стоит явно описывать в постановке.

  • REST опирается на коды HTTP: 400 — ошибка в запросе, 404 — нет ресурса, 409 — конфликт состояния, 422 — нарушено бизнес-правило, 503 — сервис недоступен. Тело ошибки желательно стандартизовать, например по RFC 9457 (Problem Details).
  • gRPC имеет собственный набор статусов: INVALID_ARGUMENT, NOT_FOUND, ALREADY_EXISTS, FAILED_PRECONDITION, UNAVAILABLE, DEADLINE_EXCEEDED и другие. Детали передаются в структурированном виде (модель google.rpc.Status). Важная особенность — дедлайны: клиент указывает, сколько готов ждать, и сервер может прекратить работу, если время вышло.
  • GraphQL обычно возвращает HTTP 200 даже при ошибках, а сами ошибки кладёт в массив errors рядом с частично заполненным data. Ответ может быть успешным наполовину: заказ найден, а данные клиента получить не удалось. Мониторинг по кодам HTTP такие ошибки не увидит. Многие команды для бизнес-ошибок используют типы-результаты в самой схеме (union вида OrderResult = Order | OrderNotFound).

Где какой стиль уместен

REST — выбор по умолчанию

  • Публичные и партнёрские API. Его умеет вызывать любой клиент на любом языке, он понятен без специальных инструментов, хорошо документируется и тестируется.
  • CRUD-сервисы и интеграции «система — система» с умеренной нагрузкой.
  • Данные, которые выгодно кэшировать: справочники, каталоги, публичный контент — здесь работают CDN и HTTP-кэш.
  • Когда команды и потребители разнородны: подрядчики, внешние партнёры, легаси-системы.

gRPC — для внутреннего высоконагруженного взаимодействия

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

Не лучший выбор, если API нужно вызывать из браузера напрямую или отдавать внешним партнёрам с разным уровнем зрелости.

GraphQL — для клиентов с разнообразными потребностями в данных

  • Backend for Frontend: веб и мобильные приложения, которым на разных экранах нужны разные срезы одних и тех же данных.
  • Агрегация нескольких источников в единый граф (federation): клиент видит один API, за которым десяток сервисов.
  • Быстро меняющийся интерфейс, где фронтенд не хочет ждать нового эндпоинта под каждый экран.

Не лучший выбор для интеграции «система — система» с фиксированным набором операций, для файлового обмена и там, где важно HTTP-кэширование.

Можно ли совмещать

Да, и в крупных системах так обычно и бывает. Типичная картина:

  • снаружи — REST для партнёров и GraphQL для собственных фронтендов;
  • внутри — gRPC между сервисами;
  • для асинхронных процессов — события через брокер (Kafka, RabbitMQ), описанные в AsyncAPI.

Ещё один распространённый приём — gRPC-шлюз (grpc-gateway, транскодирование в API-шлюзе): сервис реализует gRPC, а снаружи автоматически публикуется REST-интерфейс по аннотациям в .proto. Так внутренние потребители получают производительность, а внешние — привычный HTTP с JSON.

Что аналитику фиксировать в постановке

Независимо от стиля, в спецификации интеграции должны быть ответы на одни и те же вопросы. Различается только форма.

Что описатьRESTgRPCGraphQL
Операциипути и методысервисы и rpc-методыquery, mutation, subscription
Структурысхемы в OpenAPImessage с номерами полейтипы, input-типы, enum
Обязательностьrequiredв proto3 все поля необязательны — правила описывают отдельно или через optional и валидациюмодификатор !
Ошибкикоды HTTP и тело ошибкистатусы gRPC и деталимассив errors, коды в extensions, типы-результаты
Таймаутыожидания клиента, повторыдедлайныограничения сложности и глубины запроса
БезопасностьOAuth 2.0, ключи, mTLSmTLS, токены в метаданныхто же + права на уровне полей
Пагинацияпараметры page/cursorполя page_token в сообщенияхсоединения (connections) по спецификации Relay или аргументы first/after

Отдельно отметим proto3: в нём нет понятия «обязательное поле», а отсутствующее поле неотличимо от поля со значением по умолчанию (пустая строка, ноль). Если для бизнеса важно различать «не передано» и «передан ноль», это нужно явно заложить в контракт — через optional или обёртки (google.protobuf.Int64Value).

Типичные ошибки выбора

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

Итог

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

REST-контракты удобно проектировать в редакторе OpenAPI (см. гайд «Swagger Editor онлайн»), асинхронные — в редакторе AsyncAPI, а схемы взаимодействия сервисов — диаграммами последовательности в PlantUML. Об устройстве SOAP и о том, что в нём действительно устарело, — в статье «REST и SOAP».