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 окупаються, коли їхні сильні сторони справді потрібні, - а не «бо модно».
Схема - контракт 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 без ручних резолверів.
Вебхук - 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: черга, підпис, повтори з затримкою, події про невдалі доставки.
Документація для одержувачів: перелік типів подій із прикладами, як перевіряти підпис, політика повторів, тестові події з кабінету.
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.
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): окремий шлюз під кожен тип клієнта, що збирає дані саме під його екрани.