QA Interview Hub · База знань · Тести

API

gRPC

gRPC: що це і чим відрізняється від REST

gRPC — фреймворк віддалених викликів (RPC) від Google: клієнт викликає метод сервісу так, ніби це локальна функція. Контракт описується у .proto-файлі, з нього генеруються клієнт і серверні заглушки для багатьох мов. Транспорт — HTTP/2, формат повідомлень за замовчуванням — Protocol Buffers (бінарний, компактний, зі строгою схемою).

syntax = "proto3";
package shop.v1;

service OrderService {
  rpc GetOrder (GetOrderRequest) returns (Order);
  rpc WatchOrders (WatchRequest) returns (stream Order);
}

message GetOrderRequest { string id = 1; }
message Order {
  string id = 1;
  int64 total_cents = 2;
  Status status = 3;
}
enum Status { STATUS_UNSPECIFIED = 0; PAID = 1; SHIPPED = 2; }

Порівняння з REST і GraphQL:

  • Контракт. gRPC — contract-first, схема обов'язкова й типізована; у REST схема (OpenAPI) необов'язкова й часто розходиться з реалізацією;
  • Модель. gRPC — виклики методів (GetOrder), REST — ресурси й HTTP-дієслова, GraphQL — запит довільної форми до одного ендпоінта;
  • Продуктивність. Бінарний формат і мультиплексування HTTP/2 дають менший розмір і затримку, ніж JSON поверх HTTP/1.1;
  • Стрімінг. gRPC підтримує його «з коробки», у REST це окремі рішення (SSE, WebSocket);
  • Браузер. gRPC не працює в браузері напряму (потрібні grpc-web або Connect і проксі), REST працює скрізь;
  • Налагодження. JSON можна глянути в браузері чи curl, бінарний gRPC потребує схеми та інструментів (grpcurl, Postman).

Де застосовують: внутрішня взаємодія мікросервісів, низька затримка, стрімінг, мобільні клієнти, поліглотні команди. Публічні API для сторонніх розробників частіше лишають REST.

Protobuf: сумісність схеми, яку ламати не можна

У бінарному повідомленні поле впізнається за номером, а не за іменем. Тому номер — частина контракту: клієнти й сервери різних версій працюють одночасно, і схема має лишатися сумісною в обидва боки (backward: нові сервери читають старі повідомлення; forward: старі читають нові).

Безпечні зміни:

  • додати нове поле з новим номером (старі клієнти його ігнорують, нові отримують значення за замовчуванням від старих серверів);
  • додати нове значення enum, новий метод чи новий сервіс;
  • перейменувати поле: у бінарному форматі нічого не змінюється, але ламаються JSON-представлення та вихідний код клієнтів, тому перевіряйте це окремо.

Небезпечні зміни:

  • змінити номер поля або повторно використати номер видаленого поля: старі дані прочитаються як зовсім інше поле;
  • змінити тип на несумісний на рівні wire-формату (наприклад, string на int32); деякі цілі типи (int32, uint32, int64, bool) взаємно сумісні, але зі зрізанням значень;
  • перейти від repeated до скалярного поля чи навпаки, змінити oneof так, що поле опиняється в групі або виходить з неї;
  • видалити поле без reserved.
message User {
  reserved 4, 7;
  reserved "legacy_phone";
  string id = 1;
  string email = 2;
  optional string nickname = 3;
}

Значення за замовчуванням. У proto3 скалярне поле з нульовим значенням (0, пустий рядок, false) не передається, і отримувач не відрізняє «не задано» від «0». Якщо ця різниця важлива (наприклад, «знижка 0%» проти «знижка не вказана»), використовують optional (є ознака присутності, hasX) або обгортки типу google.protobuf.Int32Value. Перше значення enum має бути нульовим і зазвичай називається *_UNSPECIFIED.

Невідомі поля (які новіша схема додала, а старий код не знає) сучасні реалізації зберігають і передають далі, а не гублять. Номери 1–15 кодуються одним байтом тегу, тож їх лишають для найчастіших полів.

Типи викликів, статуси, дедлайни й метадані

Чотири типи RPC (HTTP/2 дозволяє багато потоків в одному з'єднанні):

  • Unary: один запит, одна відповідь (найпоширеніший, найближчий до REST);
  • Server streaming: один запит, потік відповідей (стрічка подій, великі вибірки);
  • Client streaming: потік запитів, одна відповідь наприкінці (завантаження файлу, пачка метрик);
  • Bidirectional streaming: обидва боки шлють потоки незалежно (чат, інтерактивні сесії).

Статуси. Незалежно від результату HTTP-статус відповіді — 200, а справжній результат приходить у trailers як grpc-status (число) і grpc-message. Тому тестувати треба код gRPC, а не HTTP. Основні:

  • OK (0); CANCELLED (1); UNKNOWN (2);
  • INVALID_ARGUMENT (3): запит некоректний за змістом (≈ HTTP 400);
  • DEADLINE_EXCEEDED (4): вичерпано дедлайн (≈ 504);
  • NOT_FOUND (5), ALREADY_EXISTS (6);
  • PERMISSION_DENIED (7): автентифікований, але прав немає (≈ 403);
  • RESOURCE_EXHAUSTED (8): ліміти, квоти, завеликий меседж (≈ 429);
  • FAILED_PRECONDITION (9): стан системи не дозволяє операцію;
  • UNIMPLEMENTED (12): метод не реалізовано; INTERNAL (13): помилка сервера;
  • UNAVAILABLE (14): сервіс тимчасово недоступний, можна повторити (≈ 503);
  • UNAUTHENTICATED (16): немає чинних облікових даних (≈ 401).

Деталі помилки (наприклад, які поля невалідні) передаються структурою google.rpc.Status з типізованими details (BadRequest, ErrorInfo) у заголовку grpc-status-details-bin.

Метадані — аналог HTTP-заголовків: пари ключ-значення на початку (headers) і наприкінці виклику (trailers). Ключі в нижньому регістрі, бінарні мають суфікс -bin, префікс grpc- зарезервований. Через метадані передають токен (authorization: Bearer …), ідентифікатор трасування, мову.

Дедлайн — абсолютний час, до якого клієнт готовий чекати; на мережі він передається як grpc-timeout. За замовчуванням дедлайну немає, тож виклик може висіти нескінченно: у продакшн-клієнтів його задають завжди. Дедлайн має поширюватися далі по ланцюжку сервісів, а після його закінчення сервер повинен припинити роботу. Ретраї налаштовують у service config (retryPolicy: кількість спроб, backoff, перелік повторюваних статусів, зазвичай UNAVAILABLE); повторювати варто лише ідемпотентні виклики. Інтерсептори (middleware) обгортають виклики на клієнті й сервері: логування, авторизація, метрики, трасування.

Як QA тестує gRPC-сервіс

Інструменти.

  • grpcurl: «curl для gRPC». Якщо на сервері ввімкнено server reflection, схема береться з нього; інакше передають -proto та -import-path. Метадані: -H;
  • Postman / Insomnia: gRPC-запити з імпортом .proto або через reflection, зручно для ручного дослідження й демонстрацій;
  • Згенерований клієнт у тест-фреймворку. Для TypeScript: @grpc/grpc-js з @grpc/proto-loader або типізований код із ts-proto, Buf/Connect. Це основний варіант для автотестів: типи ловлять розбіжності зі схемою ще під час компіляції;
  • buf: buf lint і buf breaking --against '.git#branch=main' у CI показують, чи не зламав PR зворотну сумісність схеми;
  • Навантаження: ghz або k6 (модуль k6/net/grpc).
# список сервісів і опис методу (потрібна reflection)
grpcurl -plaintext localhost:50051 list
grpcurl -plaintext localhost:50051 describe shop.v1.OrderService

# виклик з метаданими
grpcurl -plaintext -H 'authorization: Bearer TOKEN' \
  -d '{"id": "42"}' localhost:50051 shop.v1.OrderService/GetOrder
const deadline = new Date(Date.now() + 2000);
client.getOrder({ id: '42' }, meta, { deadline }, (err, order) => {
  expect(err?.code).toBe(grpc.status.NOT_FOUND);
});

Що перевіряти:

  • Позитивні сценарії і значення всіх полів відповіді, включно з дефолтами та optional;
  • Валідацію: порожні/завеликі/невалідні поля дають INVALID_ARGUMENT з коректними деталями, а не INTERNAL чи UNKNOWN;
  • Коди помилок: NOT_FOUND, ALREADY_EXISTS, PERMISSION_DENIED проти UNAUTHENTICATED (відсутній, прострочений, чужий токен);
  • Дедлайни: повільна залежність дає DEADLINE_EXCEEDED, сервер не продовжує роботу після скасування;
  • Стрімінг: порядок повідомлень, завершення потоку, скасування клієнтом, обрив посередині, backpressure, порожній потік;
  • Великі повідомлення: ліміт розміру (за замовчуванням приймання зазвичай 4 МБ) дає RESOURCE_EXHAUSTED, а не падіння сервера;
  • Сумісність: старий клієнт зі свіжим сервером і навпаки, невідомі поля й значення enum;
  • Ідемпотентність і ретраї: повторний UNAVAILABLE-виклик не створює дублів;
  • Безпека: TLS/mTLS, авторизація на рівні методів, reflection вимкнено у продакшні (або закрито автентифікацією).

Браузер і шлюзи. Браузерний JS не керує кадрами та trailers HTTP/2, тому прямий gRPC недоступний. Рішення: grpc-web через проксі (наприклад, Envoy), протокол Connect, або gRPC-Gateway, який надає REST/JSON поверх gRPC-сервісу. Тести слід проганяти й на шлюзі, бо там своє відображення статусів у HTTP-коди та своя серіалізація JSON.