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

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

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

4 питання

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-специфікацій