Что не так с REST
REST — де-факто стандарт для веб-API. Удобно, понятно, работает везде. Но у него есть фундаментальные ограничения, которые начинают болеть, когда нагрузка растёт или архитектура усложняется.
Первая проблема — текстовый формат. JSON хорош для чтения глазами, но тяжёл для машины. Сериализация и десериализация занимают время и CPU. При тысячах запросов в секунду это ощутимо.
Вторая — HTTP/1.1. Большинство REST API работают на нём. Head-of-line blocking никуда не девается: один зависший запрос тормозит очередь.
Третья — отсутствие строгой схемы. REST с JSON позволяет слать что угодно. Поле переименовали на бэкенде — фронтенд сломался. Автоматической валидации на уровне протокола нет.
gRPC решает все три проблемы сразу.
Что такое gRPC
gRPC — фреймворк удалённого вызова процедур от Google, открытый в 2016 году. Под капотом три вещи:
- Protocol Buffers (Protobuf) — бинарный формат сериализации
- HTTP/2 — мультиплексирование, сжатие заголовков, server push
- Строгая схема —
.proto файл описывает все методы и типы данных
Идея простая: вместо «отправь POST на /users с JSON телом» ты вызываешь метод CreateUser(user) как обычную функцию в своём коде, а gRPC сам разбирается, как передать данные по сети.
Protobuf против JSON: конкретные цифры
Разница между Protobuf и JSON ощутимая. Цифры из реальных бенчмарков:
- Размер сообщения: Protobuf в среднем в 3–5 раз меньше JSON
- Скорость сериализации: Protobuf быстрее в 5–7 раз
- Скорость десериализации: Protobuf быстрее в 3–6 раз
Пример. Объект пользователя с 10 полями в JSON занимает около 200 байт. Тот же объект в Protobuf — 50–70 байт. При миллионе запросов в день это разница в гигабайтах трафика.
Почему Protobuf такой компактный? Он не хранит названия полей — только числовые идентификаторы. Никаких фигурных скобок, кавычек, двоеточий. Только данные.
HTTP/2: что это меняет
gRPC работает только на HTTP/2. Это не ограничение, а источник преимуществ.
Мультиплексирование. Несколько запросов по одному TCP-соединению без очередей. REST на HTTP/1.1 страдает от head-of-line blocking: если один запрос завис, остальные ждут. HTTP/2 этого лишён.
Сжатие заголовков. HTTP заголовки повторяются в каждом запросе. HTTP/2 кешируует их через HPACK — ощутимая экономия при частых запросах.
Стриминг. В REST для потока данных приходится изощряться: long polling, SSE, WebSocket. В gRPC стриминг — встроенная возможность на уровне протокола.
Четыре типа взаимодействия
Это одно из главных отличий от REST. gRPC поддерживает четыре режима:
Unary — классика: один запрос, один ответ. Аналог обычного HTTP-запроса.
Server streaming — клиент отправляет один запрос, сервер отвечает потоком. Удобно для экспорта больших данных или live-обновлений.
Client streaming — клиент шлёт поток, сервер отвечает один раз. Пример: загрузка файла по частям.
Bidirectional streaming — оба конца шлют потоки независимо. Идеально для чатов, игр, realtime-систем.
В REST для bidirectional streaming нужен WebSocket, который стоит отдельно от архитектуры API. В gRPC это просто ещё один тип метода.
.proto файл: схема как контракт
Схема в gRPC — не документация, а исполняемый код. Файл .proto описывает сервис:
syntax = "proto3";
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
rpc CreateUser (CreateUserRequest) returns (User);
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
bool is_active = 4;
}
message GetUserRequest {
int32 id = 1;
}
Из этого файла генерируется клиентский и серверный код для Go, Python, Java, Node.js, C#, Ruby — практически любого популярного языка. Не нужно вручную писать HTTP-клиент, парсить JSON, следить за совместимостью полей.
Если ты изменил тип поля или удалил его — компилятор сразу скажет, какой код сломается. С REST/JSON так не получится без дополнительного инструментария вроде OpenAPI и codegen.
Где gRPC реально выигрывает
Не везде. gRPC не серебряная пуля. Вот случаи, где он даёт измеримый выигрыш:
Микросервисы. Когда у тебя 20+ сервисов, которые активно общаются между собой, gRPC снижает latency и нагрузку на сеть. Именно так его используют Google, Netflix, Dropbox внутри своей инфраструктуры.
Высокая нагрузка. Если сервис обрабатывает десятки тысяч запросов в секунду, экономия на сериализации становится заметной. Реальные кейсы показывают снижение CPU-нагрузки на 20–40% по сравнению с REST/JSON.
Мобильные клиенты. Меньший размер сообщений — меньше трафика. Для мобильных приложений на нестабильном соединении это важно. Square и Lyft перешли на gRPC для критичных запросов именно по этой причине.
Потоковые данные. Realtime-уведомления, live-трекинг, чаты — bidirectional streaming из коробки.
Полиглот-архитектуры. Когда у тебя Go на бэкенде, Python для ML, Java для легаси — gRPC генерирует клиентов для всех языков из одного .proto файла. Контракт единый.
Где REST лучше
Честно: для большинства обычных веб-проектов REST остаётся лучшим выбором.
Браузеры. gRPC не работает в браузере напрямую. Нужен gRPC-Web — проксирующий слой. Это дополнительная сложность. Для публичных API, которые используют браузеры, REST проще.
Читаемость. JSON можно открыть в браузере, в curl, в Postman. Бинарный Protobuf — нет. Дебаггинг сложнее.
Экосистема. REST/JSON поддерживают все: CDN, кеши, прокси, шлюзы. gRPC требует специфической поддержки на каждом уровне стека.
Простые проекты. Если у тебя CRUD для блога или лендинга — gRPC только добавит сложности без заметного выигрыша. Переходить стоит тогда, когда производительность стала измеримой проблемой, а не абстрактным «хотим быстрее».
Как выглядит gRPC в коде
Покажу на примере Go — один из самых популярных языков для gRPC бэкендов.
Серверная сторона:
type userServer struct {
pb.UnimplementedUserServiceServer
db *sql.DB
}
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
user, err := s.db.GetUserByID(ctx, req.Id)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
}
return &pb.User{
Id: user.ID,
Name: user.Name,
Email: user.Email,
IsActive: user.IsActive,
}, nil
}
Клиентская сторона:
conn, _ := grpc.Dial("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
client := pb.NewUserServiceClient(conn)
user, _ := client.GetUser(ctx, &pb.GetUserRequest{Id: 42})
fmt.Println(user.Name)
Никаких URL, никакого JSON, никакого парсинга. Просто вызов метода.
Коды ошибок в gRPC
REST использует HTTP статус-коды: 200, 404, 500. gRPC использует собственные коды — их 16 штук, и они более семантичны:
OK — успех
NOT_FOUND — ресурс не найден
PERMISSION_DENIED — нет прав
INVALID_ARGUMENT — неверные параметры
DEADLINE_EXCEEDED — превышен таймаут
UNAVAILABLE — сервис недоступен
DEADLINE_EXCEEDED прямо говорит, что запрос превысил таймаут — в REST для этого нет стандартного кода, используют 408 или 504 по-разному. К коду можно прикрепить детали ошибки: например, список невалидных полей через google.rpc.ErrorInfo.
Инструменты для работы с gRPC
grpcurl — curl для gRPC. Позволяет вызывать методы из командной строки:
grpcurl -d '{"id": 42}' localhost:50051 UserService/GetUser
grpc-gateway — генерирует REST/JSON шлюз поверх gRPC сервиса. Отличное решение, когда нужно поддерживать и браузерных клиентов, и эффективное внутреннее взаимодействие.
Evans — интерактивный gRPC клиент с REPL-интерфейсом. Удобен для исследования незнакомого сервиса.
Buf — инструмент для управления .proto файлами: линтинг, обнаружение ломающих изменений. Для серьёзных проектов незаменим.
Практические советы
Несколько вещей, которые сэкономят время:
Версионируй через пакеты, не через URL. В REST принято /api/v2/users. В gRPC добавь пакет: package userservice.v2. Это стандарт.
Устанавливай deadline на каждом запросе. gRPC поддерживает контексты с таймаутами — обязательно их задавай. Зависший downstream иначе заблокирует upstream по цепочке.
Не меняй номера полей в Protobuf. Числовой идентификатор поля — его постоянный ID. Удалил поле? Зарезервируй номер через reserved. Иначе получишь несовместимость бинарных данных.
TLS обязателен в продакшене. gRPC поддерживает Mutual TLS — взаимную аутентификацию клиента и сервера. Для внутренних сервисов это хорошая практика.
Server reflection — только для дев-окружения. Reflection позволяет инструментам вроде grpcurl автоматически видеть доступные методы. Удобно в разработке, но раскрывает API в продакшене — выключай.
Когда пора переходить
Конкретные признаки:
- Сервис обрабатывает больше 5 000 запросов в секунду и REST latency стала узким местом
- Количество внутренних микросервисов перевалило за 10–15 и inter-service трафик ощутим
- Нужен настоящий bidirectional streaming
- Команда работает на разных языках и синхронизация API-контрактов превратилась в боль
Если ни один из этих пунктов не про тебя — скорее всего, REST тебе достаточно. Не усложняй без причины.
Когда проект дорастает до точки, где архитектура API становится критичной, в REEXY помогут спроектировать и реализовать нужный подход — будь то gRPC, REST или гибридный шлюз. Подробности на r3xy.ru.