Когда команда проектирует новую интеграцию, вопрос «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 }
}
}
Ответ повторяет форму запроса: ровно эти поля и ничего лишнего.
Сравнение по существу
| Критерий | REST | gRPC | GraphQL |
|---|---|---|---|
| Модель | ресурсы и методы HTTP | процедуры (методы сервиса) | граф типов, запрос формирует клиент |
| Контракт | OpenAPI (необязателен, но желателен) | .proto (обязателен, без него нет кода) | схема SDL (обязательна, сервер её публикует) |
| Формат данных | JSON (текст) | Protocol Buffers (бинарный) | JSON (текст) |
| Транспорт | HTTP/1.1 или HTTP/2 | HTTP/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.
Что аналитику фиксировать в постановке
Независимо от стиля, в спецификации интеграции должны быть ответы на одни и те же вопросы. Различается только форма.
| Что описать | REST | gRPC | GraphQL |
|---|---|---|---|
| Операции | пути и методы | сервисы и rpc-методы | query, mutation, subscription |
| Структуры | схемы в OpenAPI | message с номерами полей | типы, input-типы, enum |
| Обязательность | required | в proto3 все поля необязательны — правила описывают отдельно или через optional и валидацию | модификатор ! |
| Ошибки | коды HTTP и тело ошибки | статусы gRPC и детали | массив errors, коды в extensions, типы-результаты |
| Таймауты | ожидания клиента, повторы | дедлайны | ограничения сложности и глубины запроса |
| Безопасность | OAuth 2.0, ключи, mTLS | mTLS, токены в метаданных | то же + права на уровне полей |
| Пагинация | параметры 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».
