Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «GraphQL, gRPC і вебхуки»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

5 питань

Три поширені стилі API розв'язують різні задачі.

REST - ресурси за URL і стандартні методи HTTP:

GET /api/orders/42
GET /api/orders/42/items
  • сильні сторони: простота, HTTP-кешування (CDN, ETag), зрозумілі коди відповіді, будь-який клієнт (навіть curl);
  • слабкі: клієнт отримує фіксовану форму відповіді - або зайві поля (overfetching), або кілька запитів, щоб зібрати екран (underfetching).

GraphQL - один ендпойнт, клієнт сам описує, які поля потрібні:

query {
  order(id: 42) { number total items { name qty } customer { name } }
}
  • сильні сторони: один запит на екран, сувора схема з типами, зручно для кількох клієнтів з різними потребами (веб, мобільний застосунок);
  • слабкі: складніше кешування (зазвичай POST на один URL), ризик дорогих запитів, N+1 на сервері, складніші авторизація й обмеження частоти.

gRPC - виклик віддалених процедур поверх HTTP/2 з бінарним форматом Protocol Buffers і згенерованими клієнтами:

  • сильні сторони: швидкість і компактність, суворий контракт у .proto, двобічний стримінг;
  • слабкі: браузер не викликає gRPC напряму (потрібен gRPC-Web чи шлюз), бінарні повідомлення важче налагоджувати.

Як обирати:

Ситуація Стиль
публічне API, інтеграції партнерів, вебхуки REST
багато клієнтів з різними екранами, складні зв'язані дані GraphQL
внутрішні сервіси між собою, високе навантаження, стримінг gRPC
прості дії на кшталт «надіслати лист» RPC поверх HTTP (POST /api/send-invoice)

Практичне зауваження: для типового Laravel-застосунку з одним фронтендом REST (чи Inertia без окремого API) майже завжди простіший. GraphQL і gRPC окупаються, коли їхні сильні сторони справді потрібні, - а не «бо модно».

Докладніше в документації: Вступ до GraphQL

Схема - контракт API: які типи є, які поля в них і що можна запитати. Пишеться мовою SDL:

type User {
  id: ID!
  name: String!
  email: String
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  author: User!
}

type Query {
  user(id: ID!): User
  posts(first: Int = 10): [Post!]!
}

type Mutation {
  createPost(title: String!, body: String!): Post!
}

! - поле не може бути null. [Post!]! - список, який сам не null і не містить null.

Запит (query) - читання. Клієнт вибирає лише потрібні поля, включно з вкладеними:

query {
  user(id: 7) {
    name
    posts { title }
  }
}

Відповідь повторює форму запиту: { "data": { "user": { "name": "Оля", "posts": [...] } } }.

Мутація (mutation) - зміна даних. Синтаксис як у запиту, але виконується послідовно, а результат - змінений об'єкт:

mutation {
  createPost(title: "Привіт", body: "...") { id title }
}

Підписка (subscription) - потік подій у реальному часі, зазвичай через WebSocket.

Резолвер - функція на сервері, що повертає значення поля. Для кожного поля в запиті сервер викликає його резолвер. Простим полям (name) достатньо значення з батьківського об'єкта, а для зв'язків (posts) резолвер іде в базу.

Відмінності від REST, які варто пам'ятати:

  • одна адреса (/graphql) і зазвичай метод POST;
  • помилки не через коди HTTP: відповідь часто має статус 200, а помилки лежать у масиві errors поряд із частковими даними в data;
  • інтроспекція: схему можна запитати через сам API - на цьому побудовані автодоповнення в GraphiQL і генератори типів для клієнтів.

У Laravel найпоширеніший пакет - Lighthouse: схема описується в .graphql-файлі, а директиви (@all, @find, @paginate, @create) прив'язують поля до моделей Eloquent без ручних резолверів.

Докладніше в документації: Схеми й типи GraphQL

Вебхук - HTTP-запит, який ваш сервіс надсилає на URL клієнта, коли щось сталося: замовлення оплачене, вакансію опубліковано. Клієнт не опитує API, а отримує подію сам.

Що надсилати:

{
  "id": "evt_01J9Z3K8",
  "type": "order.paid",
  "created_at": "2026-10-04T10:15:00Z",
  "api_version": "2026-09-01",
  "data": {
    "object": { "id": 42, "status": "paid", "total": "1250.00", "currency": "UAH" }
  }
}
  • id події - унікальний: одержувач за ним відкидає дублікати;
  • type - назва події у форматі ресурс.дія; клієнт підписується лише на потрібні;
  • час події - щоб одержувач міг розібратися з порядком;
  • версія формату - щоб змінювати структуру, не ламаючи наявних інтеграцій;
  • дані: або повний знімок об'єкта («товстий» вебхук), або лише ідентифікатор («тонкий», одержувач сам запитує актуальний стан через API).

Обов'язкові складники надійного вебхука:

  • підпис запиту (HMAC з секретом одержувача) і мітка часу - щоб одержувач перевірив, що запит від вас і не повторений;
  • HTTPS для URL одержувача;
  • доставка «щонайменше один раз»: при помилці чи тайм-ауті - повторні спроби з наростаючою затримкою. Одержувач має бути готовим до дублікатів;
  • швидка відповідь: одержувач підтверджує прийом кодом 2xx одразу, а обробляє асинхронно. Документуйте тайм-аут (кілька секунд);
  • журнал доставок у кабінеті: які події пішли, з яким кодом відповіді, кнопка «надіслати ще раз».

Відправлення в Laravel - лише через чергу. HTTP-запит до чужого сервера в обробнику запиту користувача - затримки й падіння, якщо одержувач повільний чи недоступний. Готове рішення - пакет spatie/laravel-webhook-server: черга, підпис, повтори з затримкою, події про невдалі доставки.

Документація для одержувачів: перелік типів подій із прикладами, як перевіряти підпис, політика повторів, тестові події з кабінету.

Докладніше в документації: Вебхуки Stripe

REST працює за схемою «запит - відповідь»: клієнт питає, сервер відповідає. Коли клієнту потрібно дізнаватися про зміни одразу, без постійного опитування, використовують постійне з'єднання.

Варіанти:

  • опитування (polling) - запит раз на N секунд. Найпростіше, але створює багато порожніх запитів і затримку до N секунд;
  • Server-Sent Events (SSE) - однобічний потік від сервера до клієнта через звичайне HTTP-з'єднання. Автоматичне перепідключення вбудоване в браузер. Добре для сповіщень, прогресу, стрічок, відповідей LLM;
  • WebSocket - двобічний канал. Потрібен, коли й клієнт часто надсилає дані: чат, спільне редагування, присутність користувачів онлайн.

Laravel Reverb - власний WebSocket-сервер Laravel, сумісний із протоколом Pusher. Застосунок транслює події, а клієнти підписуються через Laravel Echo:

php artisan install:broadcasting --reverb
php artisan reverb:start
class OrderShipped implements ShouldBroadcast
{
    public function __construct(public Order $order) {}

    public function broadcastOn(): array
    {
        return [new PrivateChannel("orders.{$this->order->user_id}")];
    }
}
Echo.private(`orders.${userId}`).listen('OrderShipped', (event) => {
  updateStatus(event.order);
});

Приватні канали авторизуються в routes/channels.php - Reverb пускає лише тих, кому дозволено слухати канал.

Що враховувати в продакшені:

  • Reverb - окремий довгоживучий процес (Supervisor, контейнер), за зворотним проксі з підтримкою WebSocket;
  • ліміт відкритих файлів ОС - кожне з'єднання займає дескриптор;
  • горизонтальне масштабування - кілька серверів Reverb обмінюються повідомленнями через Redis (REVERB_SCALING_ENABLED);
  • події через чергу: трансляція з ShouldBroadcast іде через чергу, тож без воркера повідомлення не надходять.

SSE в Laravel - response()->eventStream() у звичайному маршруті: без окремого сервера, але кожен відкритий потік тримає процес PHP.

Докладніше в документації: Laravel Reverb

API-шлюз - окремий сервіс-«вхідні двері» перед вашими API. Клієнти звертаються лише до нього, а він маршрутизує запити до внутрішніх сервісів.

клієнти → API-шлюз → сервіс замовлень
                   → сервіс користувачів
                   → сервіс платежів

Що зазвичай робить шлюз:

  • маршрутизація: /api/orders/* - до одного сервісу, /api/users/* - до іншого;
  • автентифікація: перевірка токенів чи API-ключів один раз на вході, далі - довірений заголовок з ідентифікатором користувача;
  • обмеження частоти і квоти для клієнтів і тарифів;
  • TLS-термінація, CORS, стиснення;
  • кешування відповідей;
  • трансформація: перетворення форматів, об'єднання відповідей кількох сервісів в одну;
  • спостережуваність: централізовані журнали, метрики, трасування запитів;
  • версіонування і поступове перемикання трафіку між версіями сервісу.

Приклади: Kong, Tyk, AWS API Gateway, Azure API Management, Cloudflare (частково), Traefik і Nginx як простіші варіанти.

Коли шлюз доречний:

  • кілька сервісів, які мають виглядати як одне API;
  • публічне API з ключами, тарифами, квотами й порталом для розробників;
  • потрібно централізовано застосовувати політики, а не дублювати їх у кожному сервісі.

Коли він зайвий: один моноліт на Laravel. Маршрутизація, автентифікація (Sanctum, Passport), обмеження частоти (throttle) уже є у фреймворку, а зовнішній шлюз лише додасть ще одну ланку, яка може впасти.

Ризики шлюзу:

  • єдина точка відмови і вузьке місце - потребує резервування;
  • «розумний шлюз»: бізнес-логіка, що поступово переїжджає в конфігурацію шлюзу, стає некерованою. Шлюз має займатися наскрізними задачами, а не правилами предметної області;
  • додаткова затримка на кожен запит.

Споріднений патерн - BFF (backend for frontend): окремий шлюз під кожен тип клієнта, що збирає дані саме під його екрани.

Докладніше в документації: Патерн API Gateway