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