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

Питання на співбесіді: Документація й контракти

Питання з реальних співбесід з відповідями: 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), а не пишуть вручну.

Докладніше в документації: Специфікація OpenAPI 3.2.0

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 - новий формат; варто перевірити, що версія генератора їх підтримує.

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

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) => ...).

Докладніше в документації: Laravel: тестування JSON API

Згенерований перелік ендпойнтів з параметрами - лише довідник. Розробник, що інтегрується з 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: лінтинг і пошук змін, що ламають клієнтів.

Чого не робить жоден генератор: не придумує зрозумілих описів, бізнес-правил і сценаріїв використання - ці частини пишуться людьми.

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

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' для конкретних позицій.

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

Якщо 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.

Процес, що працює:

  1. специфікація - артефакт бекенду (Scramble scramble:export чи написана вручну) у репозиторії чи CI;
  2. генерація клієнта - крок збирання фронтенду або окремий пакет;
  3. зміна API ламає збирання фронтенду, якщо клієнтський код не відповідає новому контракту, - помилка виявляється до деплою.

Що варто врахувати:

  • якість згенерованого залежить від якості специфікації: type: object без властивостей дасть Record<string, unknown>, неописані помилки - відсутність типів для них. Генерація клієнтів швидко показує прогалини в документації;
  • nullable і необов'язкові поля - розрізняти «поле може бути null» і «поля може не бути» (required). Неточність тут - джерело помилок undefined на клієнті;
  • типи не перевіряють дані під час виконання. Відповідь сервера, що не відповідає контракту, тихо пройде. Для критичних даних - валідація схемою (Zod, згенерований зі специфікації);
  • не редагувати згенерований код вручну - зміни зникнуть при наступній генерації. Розширення - обгортками;
  • версії інструментів: не всі генератори повністю підтримують OpenAPI 3.1 (типи-масиви [string, 'null'], $ref поряд з іншими полями).

Альтернатива в межах Laravel + Inertia: Wayfinder генерує типізовані функції для маршрутів і дій контролерів без проміжної специфікації.

Докладніше в документації: openapi-typescript

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-бібліотеки.

Докладніше в документації: Специфікація 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 в тестах.

Докладніше в документації: Prism: мок-сервер для OpenAPI

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) і не змінює модель схем.

Докладніше в документації: Міграція з OpenAPI 3.0 на 3.1

Проблема: бекенд змінює відповідь 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.

Ціна: інфраструктура (брокер), дисципліна в обох командах, підготовка станів провайдера. Без активного використання споживачами контракти швидко стають формальністю.

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

Стайлгайд 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, а не лише до документації.

Докладніше в документації: Spectral: лінтер для OpenAPI

Найнебезпечніші зміни 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-специфікацій