Питання на співбесіді: Документація й контракти
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
14 питань
OpenAPI (раніше Swagger) - стандартний машиночитний формат опису HTTP API у YAML чи JSON: ендпойнти, параметри, тіла запитів і відповідей, коди статусу, автентифікація.
openapi: 3.1.0
info:
title: Vacancies API
version: 1.0.0
paths:
/vacancies/{id}:
get:
summary: Отримати вакансію
parameters:
- name: id
in: path
required: true
schema: { type: integer }
responses:
'200':
description: Вакансія
content:
application/json:
schema: { $ref: '#/components/schemas/Vacancy' }
'404':
description: Не знайдено
components:
schemas:
Vacancy:
type: object
required: [id, title]
properties:
id: { type: integer }
title: { type: string }
salary_from: { type: [integer, 'null'] }
Що дає специфікація, крім документації:
- інтерактивна документація - Swagger UI, Redoc, Scalar, Stoplight Elements з кнопкою «спробувати запит»;
- генерація клієнтів - типізовані SDK для TypeScript, PHP, мобільних застосунків;
- моки - фронтенд працює з фейковим сервером до готовності бекенду;
- тести й валідація - перевірка, що реальні відповіді відповідають опису;
- лінтинг - автоматична перевірка стилю й помилок у дизайні;
- виявлення змін, що ламають клієнтів, - порівняння версій специфікації в CI;
- імпорт у Postman, Insomnia, Bruno, API-шлюзи.
Версії:
- 3.0 - найпоширеніша в старих інструментах;
- 3.1 (2021) - повна сумісність із JSON Schema 2020-12;
- 3.2 (вересень 2025) - зворотно сумісне розширення 3.1: потокові медіатипи (SSE, JSON Lines), нові поля для тегів, метод
QUERYтощо.
Для нових проєктів - 3.1 чи 3.2, з перевіркою, що потрібні інструменти їх підтримують.
Головна цінність - єдине джерело правди про контракт між бекендом і клієнтами, замість вікі-сторінок, які розходяться з кодом через тиждень.
У Laravel специфікацію найчастіше генерують з коду (Scramble, Scribe), а не пишуть вручну.
Code-first - спершу пишеться код, специфікація генерується з нього (атрибутами, аналізом коду):
контролери й ресурси Laravel → Scramble/Scribe → openapi.json → документація
Design-first - спершу пишеться специфікація (контракт), її обговорюють і погоджують, а потім за нею пишуть код і клієнтів:
openapi.yaml (рев'ю) → моки для фронтенду → реалізація бекенду → перевірка відповідності
Переваги code-first:
- специфікація не розходиться з кодом - вона з нього й береться;
- швидкий старт, мало додаткової роботи;
- зручно для невеликих команд, де бекенд і фронтенд пишуть ті самі люди.
Недоліки code-first:
- дизайн API формується реалізацією: назви полів і структура відповідей відображають внутрішню модель (колонки таблиць), а не потреби клієнтів;
- обговорити API можна лише після того, як його написано;
- генератор бачить не все: деталі на кшталт можливих помилок чи прикладів доводиться доповнювати.
Переваги design-first:
- контракт - до коду: фронтенд, мобільна команда й партнери погоджують API заздалегідь і працюють паралельно з моками;
- кращий дизайн: API проєктують з боку споживача, з рев'ю, як код;
- генерація серверних заготовок і валідація запитів за специфікацією.
Недоліки design-first:
- специфікацію треба підтримувати вручну - і перевіряти, що реалізація їй відповідає (тести контракту, валідація відповідей у тестах), інакше вона застаріває;
- більше процесу й інструментів.
Що обирають на практиці:
- внутрішнє API одного продукту (Laravel + SPA однієї команди) - code-first зі Scramble: мінімум зусиль, завжди актуально;
- публічне API, партнерські інтеграції, кілька команд-споживачів - design-first або гібрид: дизайн погоджується в специфікації, а згенерована з коду специфікація порівнюється з погодженою в CI;
- будь-який підхід виграє від лінтингу специфікації і перевірки змін, що ламають клієнтів, у CI.
Докладніше в документації: Документація OpenAPI для початківців
Scramble генерує специфікацію OpenAPI з коду Laravel автоматично, без анотацій над кожним методом. Він аналізує:
- маршрути - шляхи, методи, параметри маршруту й прив'язку моделей;
- валідацію - правила з Form Request чи
$request->validate()стають описом тіла запиту й параметрів (required|email|max:255→ обов'язковий рядок формату email); - відповіді - API-ресурси (
JsonResource), повернені моделі, пагінацію,response()->json(...); - помилки - 422 для валідації, 404 для прив'язки моделей, 403 для авторизації, 401 для автентифікації.
composer require dedoc/scramble
Після встановлення документація доступна за адресою /docs/api (інтерфейс на основі Stoplight Elements), а сама специфікація - /docs/api.json.
Доповнення там, де аналізу коду недостатньо:
/**
* Список вакансій.
*
* Повертає опубліковані вакансії, найновіші першими.
*/
public function index(IndexVacancyRequest $request)
{
// ...
}
PHPDoc-коментар стає описом операції. Для складних випадків - атрибути Scramble й розширення.
Що варто налаштувати:
- доступ: за замовчуванням документація відкрита лише в локальному оточенні, у продакшені доступ визначає gate
viewApiDocs; - які маршрути документувати (за замовчуванням - з префіксом
api); - автентифікація: опис схеми (Bearer-токен Sanctum), щоб кнопка «спробувати» працювала;
- експорт:
php artisan scramble:exportзаписує специфікацію у файл - для генерації клієнтів і перевірок у CI.
Альтернатива - Scribe: генерує документацію й колекції Postman, бере опис з анотацій і може робити справжні запити до ендпойнтів, щоб отримати приклади відповідей.
Обмеження автоматичної генерації:
- точність залежить від коду: динамічні масиви в
toArray(), умовна логіка, дані з сервісів можуть описатися неточно - результат треба переглядати; - якість опису - на вас: назви полів, приклади, пояснення бізнес-правил генератор не придумає;
- вбудовані JSON:API-ресурси Laravel 13 - новий формат; варто перевірити, що версія генератора їх підтримує.
Laravel має набір перевірок для JSON-відповідей. Головне - обирати перевірку під те, що саме гарантує API.
it('returns a vacancy', function () {
$vacancy = Vacancy::factory()->create(['title' => 'Laravel Developer', 'salary_from' => 3000]);
$this->getJson("/api/vacancies/{$vacancy->id}")
->assertOk()
->assertJsonPath('data.title', 'Laravel Developer')
->assertJsonPath('data.salary_from', 3000)
->assertJsonStructure([
'data' => ['id', 'title', 'salary_from', 'company' => ['id', 'name']],
]);
});
Основні методи:
| Метод | Що перевіряє |
|---|---|
assertJson([...]) |
відповідь містить ці дані (інші поля дозволені) |
assertExactJson([...]) |
відповідь дорівнює цьому JSON |
assertJsonPath('data.title', '...') |
значення за шляхом |
assertJsonStructure([...]) |
наявність ключів (без значень) |
assertExactJsonStructure([...]) |
ключі - і жодних зайвих |
assertJsonCount(20, 'data') |
кількість елементів |
assertJsonMissingPath('data.password') |
поля немає |
assertJsonValidationErrors(['email']) |
помилки валідації для полів |
Методи запиту: getJson, postJson, patchJson, deleteJson - автоматично додають Accept: application/json, тож помилки валідації приходять як JSON 422, а не редирект.
Що варто перевіряти в API-тестах:
- контракт: структура відповіді й типи - те, на що покладаються клієнти;
- приховані поля:
assertJsonMissingPath('data.password_hash')- захист від випадкового витоку при зміні ресурсу; - коди статусу для кожного сценарію: 201 при створенні, 422 при помилках, 403 для чужого ресурсу, 404 для неіснуючого;
- авторизацію: запит від іншого користувача (
actingAs($stranger)) - найчастіше забута перевірка; - пагінацію й фільтри:
assertJsonCount, перевірка, що фільтр справді відсікає записи.
Пастки:
assertJsonне перевіряє відсутність полів - нове випадкове поле в ресурсі тест не помітить. Для захисту контракту -assertExactJsonStructureабо перевірка відповідності специфікації OpenAPI;- типи:
assertJsonPath('data.price', 100)і'100.00'- різні значення; рішення про формат грошей має бути зафіксоване тестом; - дати порівнюють у форматі, який віддає API (ISO 8601 з поясом), а не як об'єкти Carbon.
Для складних перевірок - fluent-API assertJson(fn (AssertableJson $json) => ...).
Згенерований перелік ендпойнтів з параметрами - лише довідник. Розробник, що інтегрується з API, має відповіді на питання «з чого почати» і «що робити, коли щось пішло не так».
1. Швидкий старт: як отримати ключ, перший запит, який можна скопіювати й виконати за хвилину:
curl https://api.example.com/v1/vacancies?city=kyiv \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
2. Автентифікація: як отримати й оновити токен, термін дії, області доступу (scopes).
3. Приклади - для кожного запиту й відповіді. Реалістичні дані, а не "string" і 0. В OpenAPI - поля example/examples у схемах і медіатипах. Кілька прикладів для різних сценаріїв (вакансія із зарплатою і без).
4. Помилки - не менш детально, ніж успіх:
- формат помилки (Problem Details чи власний) з прикладами;
- коди помилок бізнес-логіки й що з ними робити (
insufficient_balance- поповнити рахунок); - які помилки тимчасові й можна повторити (429, 503), а які ні.
5. Загальні правила API - один раз для всіх ендпойнтів:
- пагінація, фільтрація, сортування;
- формати дат, грошей, ідентифікаторів;
- ліміти частоти й заголовки, що їх показують;
- ідемпотентність і повтори;
- версіонування й політика змін.
6. Сценарії (гайди): типові задачі, що складаються з кількох запитів - «створити замовлення й оплатити», «синхронізувати каталог». Довідник ендпойнтів їх не пояснює.
7. Журнал змін (changelog) і дати застарівання.
8. SDK і колекції: офіційні клієнти, колекція Postman/Bruno, посилання на специфікацію OpenAPI для генерації власних клієнтів.
Якість, яку легко перевірити:
- новий розробник робить перший успішний запит без запитань до команди;
- кожен ендпойнт має приклад відповіді й перелік можливих помилок;
- приклади в документації перевіряються автоматично (контрактні тести, валідація за специфікацією) - інакше вони застаріють першими.
Інтерактивність: кнопка «спробувати» в документації (Swagger UI, Scalar, Stoplight Elements) з тестовим оточенням (sandbox) пришвидшує інтеграцію - але ніколи з продакшен-даними.
Докладніше в документації: Документація OpenAPI для початківців
Обидва генерують документацію API з коду Laravel, але різними способами.
Scramble:
- статичний аналіз коду: читає маршрути, Form Request, правила валідації, API-ресурси й типи повернення - без анотацій;
- результат - специфікація OpenAPI 3.1 і веб-інтерфейс документації на її основі;
- документація оновлюється автоматично разом з кодом;
- доповнення через PHPDoc і атрибути; платна версія підтримує популярні пакети (Laravel Data, Query Builder).
Scribe:
- комбінація джерел: правила валідації з Form Request, PHPDoc-анотації (
@group,@bodyParam,@response) і, за бажанням, реальні запити до ендпойнтів у локальному оточенні, щоб отримати справжні приклади відповідей; - генерує HTML-документацію з прикладами коду кількома мовами й кнопкою «Try It Out», а також колекцію Postman і специфікацію OpenAPI;
- документація генерується командою (
php artisan scribe:generate) - статичні файли, які можна викласти будь-де.
Як обрати:
| Критерій | Scramble | Scribe |
|---|---|---|
| зусилля на старті | мінімальні | більше анотацій |
| актуальність | завжди з коду | після перегенерації |
| реальні приклади відповідей | з типів і ресурсів | можуть братися з живих запитів |
| основний артефакт | OpenAPI-специфікація | HTML-документація + Postman |
| точність при складній логіці | залежить від аналізу коду | контролюється анотаціями |
Практичні поради:
- внутрішнє API для власного фронтенду - Scramble: найменше підтримки, специфікація придатна для генерації TypeScript-клієнта;
- публічна документація з гайдами й багатьма прикладами - Scribe чи окремий інструмент документації поверх експортованої специфікації;
- реальні запити в Scribe виконуються з даними й побічними ефектами - їх налаштовують лише для безпечних ендпойнтів і окремої бази;
- у будь-якому разі експортовану специфікацію варто тримати в репозиторії й перевіряти в CI: лінтинг і пошук змін, що ламають клієнтів.
Чого не робить жоден генератор: не придумує зрозумілих описів, бізнес-правил і сценаріїв використання - ці частини пишуться людьми.
Fluent-перевірки дають змогу описати відповідь повністю: значення, типи, вкладені структури й відсутність зайвих полів.
use Illuminate\Testing\Fluent\AssertableJson;
$this->getJson('/api/vacancies?filter[city]=kyiv')
->assertOk()
->assertJson(fn (AssertableJson $json) => $json
->has('data', 3, fn (AssertableJson $vacancy) => $vacancy
->where('city', 'kyiv')
->whereType('id', 'integer')
->whereType('salary_from', 'integer|null')
->has('company', fn (AssertableJson $company) => $company
->hasAll(['id', 'name'])
->missing('owner_email')
->etc()
)
->etc()
)
->has('meta')
->has('links')
);
Основні методи:
where('key', $value)- точне значення; можна передати замикання для власної умови;whereType('key', 'string')- тип (string,integer,array,null, через|- кілька);has('key'),has('items', 3)- наявність і кількість;has('data', 3, fn ...)- кількість і перевірка першого елемента колекції;each(fn ...)- перевірка кожного елемента;hasAll([...]),hasAny([...]),missing('key'),missingAll([...]).
Найважливіше - строгість за замовчуванням. Кожен рівень, перевірений через замикання, вимагає, щоб усі ключі на цьому рівні були перевірені. Незгадане поле - провал тесту. etc() явно дозволяє «інші поля теж можуть бути».
Це захищає від головного ризику API: нове поле, що випадково потрапило у відповідь (наприклад, хтось додав модель цілком замість ресурсу, і в JSON з'явилися внутрішні поля). Без etc() тест це помітить.
Коли що обирати:
- простий тест одного значення -
assertJsonPath; - контракт ресурсу (усі поля й типи, нічого зайвого) - fluent-перевірки без
etc()на ключових рівнях; - дуже великі відповіді - перевірка відповідності специфікації OpenAPI (бібліотеки валідації відповідей за схемою), щоб не дублювати опис контракту в тестах і в документації.
Пастка: has('data', 3, ...) перевіряє замиканням лише перший елемент. Для перевірки всіх - has('data', 3) і окремо ->each(...) або ->has('data.0', ...)/'data.1' для конкретних позицій.
Якщо API описано специфікацією OpenAPI, клієнтський код не треба писати й підтримувати вручну: типи запитів і відповідей генеруються з контракту.
Варіант 1 - лише типи (openapi-typescript + openapi-fetch):
npx openapi-typescript ./openapi.json -o ./src/api/schema.d.ts
import createClient from 'openapi-fetch';
import type { paths } from './api/schema';
const api = createClient<paths>({ baseUrl: '/api' });
const { data, error } = await api.GET('/vacancies/{id}', {
params: { path: { id: 42 } },
});
// data - точний тип відповіді 200, error - тип помилки з опису
Шлях, параметри й тип відповіді перевіряються компілятором: друкарська помилка в URL чи відсутній обов'язковий параметр - помилка TypeScript, а не 404 у продакшені.
Варіант 2 - повний SDK (OpenAPI Generator, Hey API, Orval, Kiota): згенеровані класи чи функції для кожної операції, моделі, інколи - готові хуки для TanStack Query.
Процес, що працює:
- специфікація - артефакт бекенду (Scramble
scramble:exportчи написана вручну) у репозиторії чи CI; - генерація клієнта - крок збирання фронтенду або окремий пакет;
- зміна API ламає збирання фронтенду, якщо клієнтський код не відповідає новому контракту, - помилка виявляється до деплою.
Що варто врахувати:
- якість згенерованого залежить від якості специфікації:
type: objectбез властивостей дастьRecord<string, unknown>, неописані помилки - відсутність типів для них. Генерація клієнтів швидко показує прогалини в документації; nullableі необов'язкові поля - розрізняти «поле може бутиnull» і «поля може не бути» (required). Неточність тут - джерело помилокundefinedна клієнті;- типи не перевіряють дані під час виконання. Відповідь сервера, що не відповідає контракту, тихо пройде. Для критичних даних - валідація схемою (Zod, згенерований зі специфікації);
- не редагувати згенерований код вручну - зміни зникнуть при наступній генерації. Розширення - обгортками;
- версії інструментів: не всі генератори повністю підтримують OpenAPI 3.1 (типи-масиви
[string, 'null'],$refпоряд з іншими полями).
Альтернатива в межах Laravel + Inertia: Wayfinder генерує типізовані функції для маршрутів і дій контролерів без проміжної специфікації.
JSON:API - специфікація формату JSON-відповідей і правил запитів для API. Замість того, щоб кожна команда вигадувала власну структуру, вона фіксує готові рішення.
{
"data": {
"type": "posts",
"id": "1",
"attributes": { "title": "Laravel 13", "published_at": "2026-09-30T10:00:00Z" },
"relationships": {
"author": { "data": { "type": "users", "id": "7" } }
}
},
"included": [
{ "type": "users", "id": "7", "attributes": { "name": "Оля" } }
]
}
Що стандартизує:
- структура ресурсу:
type,id(завжди рядок),attributes,relationships,links,meta; include- пов'язані ресурси в масивіincluded, кожен один раз, навіть якщо на нього посилаються десятки записів;- sparse fieldsets -
fields[posts]=title; - сортування, пагінація, фільтрація - назви параметрів (
sort=-published_at,page[...],filter[...]); - формат помилок - масив
errorsзstatus,code,title,detail,source.pointer; - медіатип -
application/vnd.api+json.
Laravel 13 має вбудовані JSON:API-ресурси:
php artisan make:resource PostResource --json-api
class PostResource extends JsonApiResource
{
public $attributes = ['title', 'body', 'published_at'];
public $relationships = ['author', 'comments'];
}
JsonApiResource формує структуру data/attributes/relationships, обробляє include і fields із запиту, серіалізує зв'язки лише коли клієнт їх запросив і виставляє правильний Content-Type. Глибину вкладених include обмежує JsonApiResource::maxRelationshipDepth(). Розбір фільтрів і сортування Laravel лишає за пакетами на кшталт spatie/laravel-query-builder.
Переваги стандарту:
- не потрібно вигадувати й документувати формат - достатньо послатися на специфікацію;
- готові клієнтські бібліотеки вміють нормалізувати
included, будувати запити зincludeіfields; - дедуплікація пов'язаних ресурсів зменшує розмір відповідей.
Недоліки:
- багатослівність: для простих API структура надлишкова, а клієнту без бібліотеки доводиться «склеювати»
relationshipsзincluded; - рядкові
idі обгортки незвичні для фронтенд-розробників; - проблеми продуктивності з
includeна сервері лишаються: кожен дозволений зв'язок треба завантажувати жадібно.
Коли обирати: публічні API й інтеграції, де передбачуваність формату важить більше за компактність, або коли клієнти вже використовують JSON:API-бібліотеки.
Коли контракт API погоджено (специфікація OpenAPI), фронтенд не повинен чекати на готовий бекенд. Мок-сервер відповідає за специфікацією.
Prism - мок-сервер, що читає OpenAPI:
npx @stoplight/prism-cli mock openapi.yaml
# слухає на http://127.0.0.1:4010
- статичні відповіді з полів
example/examplesспецифікації; - динамічні (
--dynamic) - згенеровані дані, що відповідають схемам; - валідація запитів: запит з неправильним тілом чи без обов'язкового параметра отримає 422 - фронтенд одразу бачить, що надсилає не те;
- вибір сценарію заголовком
Prefer: code=404чиPrefer: example=empty- перевірка обробки помилок; - режим проксі - перевіряє, що справжній сервер відповідає специфікації (контрольна точка між бекендом і контрактом).
MSW (Mock Service Worker) - моки на рівні клієнта:
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/vacancies/:id', ({ params }) =>
HttpResponse.json({ data: { id: Number(params.id), title: 'Laravel Developer' } }),
),
http.post('/api/applications', () =>
HttpResponse.json({ errors: { email: ['Обов\'язкове поле'] } }, { status: 422 }),
),
];
Service Worker у браузері (чи перехоплювач у Node для тестів) відповідає на запити застосунку. Код застосунку не знає про моки - робить звичайні fetch.
Порівняння:
- Prism - окремий сервер, що бере дані зі специфікації: моки не розходяться з контрактом;
- MSW - моки в коді, повний контроль над сценаріями (затримки, помилки мережі, стан між запитами), спільні для розробки, тестів і Storybook. Але їх треба підтримувати вручну або генерувати зі специфікації.
Головний ризик моків - розходження з реальністю. Фронтенд «працює» з моками, а з реальним API - ні. Захист:
- генерувати моки й типи з тієї самої специфікації, що й документацію;
- контрактні тести на бекенді (відповіді відповідають специфікації);
- регулярна перевірка на реальному тестовому оточенні до релізу.
Для бекенду на Laravel аналогічна задача - Http::fake() для сторонніх API в тестах.
OpenAPI 3.0 використовував власний діалект JSON Schema - «розширену підмножину»: частину ключових слів підтримував інакше, частину не підтримував, а частину додав від себе (nullable). Через це одну й ту саму схему не можна було напряму використати і в OpenAPI, і в бібліотеках валідації JSON Schema.
OpenAPI 3.1 зробив схеми повністю сумісними з JSON Schema 2020-12.
Основні зміни в схемах:
1. nullable прибрано - замість нього масив типів:
# 3.0
salary_from:
type: integer
nullable: true
# 3.1
salary_from:
type: [integer, 'null']
2. Приклади: example (одне значення) застаріло на користь examples - масиву, як у JSON Schema.
3. exclusiveMinimum / exclusiveMaximum - тепер числа, а не булеві прапорці.
4. $ref поруч з іншими ключовими словами - дозволено (у 3.0 сусідні ключі ігнорувалися), тож можна послатися на схему й додати опис.
5. Нові можливості JSON Schema: const, if/then/else, prependItems, $defs, unevaluatedProperties, оголошення діалекту через $schema.
6. Опис файлів: замість format: binary - contentMediaType і contentEncoding.
Інші зміни специфікації:
- вебхуки (
webhooks) - опис запитів, які API надсилає клієнтам; pathsстав необов'язковим - документ може описувати лише компоненти чи вебхуки;info.summary, ідентифікатор ліцензії SPDX.
Чому сумісність важлива на практиці:
- одна схема - кілька застосувань: та сама схема валідує запити на сервері, генерує типи (TypeScript, Zod), описує дані в документації й перевіряє відповіді в контрактних тестах - без перетворень і розбіжностей;
- екосистема JSON Schema (валідатори, генератори форм, редактори) працює з OpenAPI-схемами напряму;
- менше «дивних» відмінностей, через які інструменти по-різному трактували одну специфікацію.
Міграція 3.0 → 3.1 - не лише заміна номера версії: nullable, example, exclusiveMinimum треба переписати. І перевірити, що всі інструменти ланцюжка (генератори клієнтів, UI документації, шлюзи) підтримують 3.1 - деякі досі працюють коректно лише з 3.0.
OpenAPI 3.2 (2025) розширює 3.1 зворотно сумісно (потокові медіатипи, ієрархічні теги, метод QUERY) і не змінює модель схем.
Проблема: бекенд змінює відповідь API - перейменовує поле, змінює формат дати. Його тести зелені, тести фронтенду (з моками) теж зелені. А в продакшені мобільний застосунок падає. Ніхто не перевіряв, що реальний провайдер відповідає очікуванням реальних споживачів.
Контрактне тестування, кероване споживачем (consumer-driven contract testing), закриває саме цю прогалину. Найвідоміший інструмент - Pact.
Як це працює:
1. Споживач (фронтенд, мобільний застосунок, інший сервіс) у своїх тестах описує взаємодії:
provider
.uponReceiving('запит вакансії за id')
.withRequest({ method: 'GET', path: '/api/vacancies/42' })
.willRespondWith({
status: 200,
body: { data: { id: like(42), title: like('Laravel Developer'), salary_from: integer(3000) } },
});
Тест споживача виконується проти мок-сервера Pact і генерує контракт (JSON-файл) - перелік запитів і того, що споживач справді використовує з відповідей.
2. Контракт публікується (Pact Broker / PactFlow).
3. Провайдер (бекенд) у своєму CI перевіряє контракт: Pact відтворює запити з контракту проти справжнього застосунку й порівнює відповіді. Для станів («існує вакансія 42») провайдер готує дані.
4. Перед деплоєм перевіряється: чи сумісна ця версія провайдера з версіями споживачів, що зараз у продакшені (can-i-deploy).
Чим відрізняється від інших підходів:
- від перевірки за OpenAPI: специфікація описує, що провайдер може повернути. Контракт споживача - що він використовує. Видалення поля, яким ніхто не користується, не ламає контрактів - а зміна поля, яке читає мобільний застосунок, ламає, навіть якщо специфікацію оновили;
- від e2e-тестів: не потрібно піднімати всю систему разом - кожна сторона перевіряється окремо й швидко.
Коли варто:
- кілька незалежних команд і сервісів, що деплояться окремо;
- мобільні застосунки (старі версії живуть у користувачів місяцями);
- мікросервіси, що спілкуються HTTP чи повідомленнями.
Коли зайве: моноліт Laravel з фронтендом в одному репозиторії і спільним деплоєм - там простіше спільні типи (згенеровані з OpenAPI чи Wayfinder) і звичайні тести API.
Ціна: інфраструктура (брокер), дисципліна в обох командах, підготовка станів провайдера. Без активного використання споживачами контракти швидко стають формальністю.
Стайлгайд API («ресурси в множині», «поля в snake_case», «кожна помилка має опис») у вікі мало хто читає. Якщо API описано специфікацією OpenAPI, правила можна перевіряти автоматично, як код лінтером.
Spectral - лінтер для OpenAPI (і AsyncAPI) з набором вбудованих правил і власними правилами:
# .spectral.yaml
extends: ["spectral:oas"]
rules:
operation-description: error
paths-kebab-case:
description: Шляхи мають бути в kebab-case
severity: error
given: $.paths[*]~
then:
function: pattern
functionOptions:
match: "^(/[a-z0-9-{}]+)+$"
properties-snake-case:
description: Властивості схем - snake_case
severity: warn
given: $.components.schemas[*].properties[*]~
then:
function: casing
functionOptions: { type: snake }
errors-documented:
description: Кожна операція описує відповідь 4xx
severity: warn
given: $.paths[*][*].responses
then:
function: schema
functionOptions:
schema: { anyOf: [{ required: ["400"] }, { required: ["404"] }, { required: ["422"] }] }
npx @stoplight/spectral-cli lint openapi.json
given - JSONPath до частини документа, then - перевірка (pattern, casing, truthy, schema, власні функції на JavaScript).
Що варто перевіряти:
- узгодженість: однаковий стиль назв, однакова пагінація й формат помилок у всіх ендпойнтах;
- повноту: описи операцій, приклади, задокументовані помилки й автентифікація;
- безпеку: кожна операція має схему безпеки, немає ендпойнтів без автентифікації випадково;
- заборонені практики: дієслова в шляхах,
200з тілом помилки, відсутність обмеження розміру сторінки.
У CI разом з лінтером - пошук змін, що ламають клієнтів: порівняння специфікації гілки зі специфікацією основної гілки (інструменти на кшталт oasdiff). Видалене поле, новий обов'язковий параметр, змінений тип - помилка збирання, якщо зміна не позначена як навмисна.
Як впроваджувати:
- починати з попереджень, а не помилок, - інакше існуюче API не пройде перевірку;
- нові правила - як
errorлише для нових ендпойнтів чи після виправлення старих; - стайлгайд у вигляді набору правил Spectral - виконуваний, і його можна перевикористати між командами (публікуючи як npm-пакет).
Для Laravel з генерацією специфікації (Scramble): експорт специфікації в CI → лінтинг → порівняння з основною гілкою. Так правила застосовуються до реального API, а не лише до документації.
Найнебезпечніші зміни API - ті, що непомітно ламають клієнтів: розробник «трохи покращив» ресурс, тести бекенду зелені, а інтеграція партнера падає. Захист - зробити зміни контракту видимими й перевірюваними.
Що ламає клієнтів (breaking changes):
- видалення чи перейменування поля, ендпойнта, параметра;
- зміна типу поля (число → рядок) чи формату (дата без поясу → з поясом);
- новий обов'язковий параметр запиту чи поле тіла;
- звуження допустимих значень (нові обмеження валідації, видалене значення enum);
- зміна кодів статусу й формату помилок;
- зміна поведінки за замовчуванням (сортування, розмір сторінки).
Що зазвичай безпечно: нові необов'язкові параметри, нові поля у відповіді, нові ендпойнти. «Зазвичай» - бо клієнт зі строгою десеріалізацією чи вичерпною перевіркою enum може зламатися й від нового значення. Це варто прямо прописати в політиці API: «клієнти мають ігнорувати невідомі поля й значення».
Автоматична перевірка в CI:
oasdiff breaking main-openapi.yaml branch-openapi.yaml --fail-on ERR
oasdiff changelog main-openapi.yaml branch-openapi.yaml
breaking- список змін, що ламають, з рівнями серйозності; збирання падає, якщо зміна не дозволена;changelog- людиночитний перелік змін, який можна використати в описі pull request і журналі змін.
Специфікацію для порівняння беруть з основної гілки (згенеровану Scramble чи написану вручну) - так навіть «непомітні» зміни ресурсу Laravel стають видимими в рев'ю.
Журнал змін для споживачів:
- дата й версія кожної зміни, групування: додано, змінено, застаріло, видалено;
- посилання на документацію й інструкція з міграції для кожної зміни, що ламає;
- оголошення заздалегідь: дата застарівання і дата видалення; для відповідей - заголовки
DeprecationіSunset; - канал повідомлень для інтеграторів (розсилка, RSS, сторінка статусу), а не лише сторінка, яку ніхто не відкриває.
Процес для змін, що ламають: нова версія ендпойнта чи поля, паралельна підтримка старого, моніторинг використання застарілого (логування запитів зі старими полями й версіями, щоб знати, хто ще залежить), повідомлення конкретним клієнтам, лише потім видалення.
Найкращий журнал змін - коротший: більшість змін, що ламають, можна замінити адитивними (нове поле поруч зі старим), і тоді клієнтам взагалі нічого не треба робити.
Докладніше в документації: oasdiff: порівняння OpenAPI-специфікацій