Skip to main content

Дизайн REST API, який виживає: версіонування, auth, помилки і те, чого ніхто не вчить

Задеплоїти endpoint може будь-хто. API, які переживають п'ять років консьюмерів, міграцій та інцидентів о 3-й ночі, спроєктовані навколо нудних частин: версіонування, з яким можна жити, auth, що відмовляє в закритий стан, помилки, які парсить машина, і пагінація, яка не бреше.

Дизайн REST API, який виживає: версіонування, auth, помилки і те, чого ніхто не вчить

Кожен туторіал вчить одному й тому самому REST API: п’ять роутів, happy path, JSON на вході, JSON на виході, готово за вечір. Потім API зустрічається з реальністю — мобільний клієнт, який три тижні не може оновитися, партнерська інтеграція, написана поверх одруківки, яку тепер не можна виправити, security-рев’ю, баг пагінації, який нікого не списав двічі, але всім усе показав двічі, — і ви виявляєте, що туторіал покрив легкі 20%. Про решту 80% і написано цей пост.

Це рішення, які мені доводилося захищати (або про які шкодувати) за роки production-API, — у тому порядку, в якому вони зазвичай кусають. І не випадково це саме ті теми, які промацує system design співбесіда, коли інтерв’юер каже: «а тепер спроєктуйте для цього API».

Як версіонувати API, який не можна ламати?

Версіонуйте з першого дня, в URL, і ставтеся до кожного опублікованого поля як до контракту, якого дотримуватиметеся роками. У момент, коли один консьюмер, якого ви не контролюєте, викочує код поверх вашого API, «потім почистимо» перетворюється на фікцію: перейменувати поле — breaking change, видалити поле — breaking change, змінити тип — це інцидент із вашим прізвищем. /v1/ у шляху негламурний і трохи неелегантний — і це підхід, у якого нудні режими відмови, а це найвищий комплімент у дизайні API.

Перш ніж іти рішення за рішенням, ось увесь пост однією таблицею — туторіальна відповідь, відповідь, що переживає п’ять років консьюмерів, і те, у що реально обходиться зрізаний кут, коли надходить рахунок:

РішенняТуторіальна відповідьВідповідь, що виживаєУ що обходиться зрізаний кут
ВерсіонуванняДодавайте і перейменовуйте поля вільно/v1/ з першого дня; після публікації — лише адитивні зміниКожне перейменування — інцидент усередині чужого застосунку
Auth за замовчуваннямДодайте auth на ті endpoint-и, яким він потрібенDeny by default; публічні роути позначені явноТой єдиний endpoint, який хтось забув, — краулер знайде його раніше за вас
АвторизаціяВалідний токен означає, що викликачеві можнаПеревіряйте володіння на кожному читанні ресурсуДані вашого клієнта, віддані іншому клієнтові
ПомилкиHTTP-статус плюс повідомлення прозоюМашинний код, людське повідомлення, correlation IDІнтегратори гадають; їхній баг-репорт стає вашим дзвінком о 3-й ночі
Пагінація?page=3Непрозорий cursor зі змістом «продовжуй після цього рядка»Елементи дублюються і зникають під записами; глибокі сторінки сканують і викидають
Аудиторія документаціїЛюди, які її погортаютьНайбуквальніший консьюмер, який у вас коли-небудь будеАгент галюцинує параметр, який ви лишили розпливчастим

Справжня дисципліна не в схемі URL, а у звичці до адитивних змін. Нова можливість? Нове опціональне поле, новий endpoint — і ніколи не змінений сенс наявного. Команди, які засвоїли правило «опубліковано — значить назавжди», проєктують поля уважніше до публікації, і в цьому й полягає реальна користь: дисципліна версіонування — здебільшого forcing function, що змушує подумати двічі.

Як виглядає auth, що «fails closed»?

Кожен endpoint вимагає автентифікації, якщо він явно й свідомо не позначений як публічний — патерн deny-by-default middleware, де забута конфігурація роуту означає, що він закритий, а не відкритий. Повторювана катастрофа індустрії в API — не зламана криптографія, а endpoint, який хтось забув захистити і який знайшов краулер. Fail-closed дизайн робить таку помилку структурно неможливою: лінивий шлях і безпечний шлях — той самий шлях.

Авторизація заслуговує на ту саму параною рівнем глибше: автентифікація каже, хто ви, авторизація — що ваше, і зазор між ними — класичний злам. GET /orders/12345 з валідним токеном все одно зобов’язаний перевірити, що замовлення 12345 належить користувачеві цього токена — баг відсутньої перевірки володіння (IDOR мовою безпеки) залишається найпоширенішою серйозною вразливістю API на код-рев’ю, і згенерований код відтворює його з ентузіазмом, бо happy-path-версія виглядає ідентично.

Чому відповіді про помилки заслуговують часу на дизайн?

Бо ваші помилки — теж API, той самий, який о 3-й ночі споживає втомлений інтегратор, вирішуючи, чий це баг — його чи ваш. Живуча відповідь про помилку несе три речі: машиночитний код (insufficient_funds, а не просто HTTP 400), людиночитне повідомлення з іменем проблемного поля і correlation ID, за яким ваші логи та його баг-репорт знаходять одне одного. Самого HTTP-статусу замало: 400, що означає п’ять різних речей, змушує кожного консьюмера парсити вашу прозу.

Передбачуваність — фіча. Сюрприз — витрата. API, які називають «приємними в інтеграції», рідко бувають хитрими — вони роблять одне й те саме всюди.

Консистентність важливіша за багатство. Один error envelope, ідентична форма на кожному endpoint, задокументовано один раз. API, які описують як «приємні в інтеграції», рідко роблять щось хитре — вони роблять одне й те саме передбачуване всюди, тому обробка помилок в інтегратора — одна функція, а не по одній на endpoint. Передбачуваність — фіча; сюрприз — витрата, той самий урок, який durability викладає на рівні архітектури.

Що не так із пагінацією за номерами сторінок?

Offset-пагінація (?page=3) бреше за конкурентних записів: рядки, вставлені або видалені, поки консьюмер іде сторінками, змушують елементи з’являтися двічі або зникати, а глибокі offset-и карають базу повним scan-and-discard. Вона виживає в туторіалах, бо тривіальна і виглядає правильно в демо зі статичними даними. Cursor-пагінація — непрозорий токен зі змістом «продовжуй після цього рядка» — залишається коректною під записами і швидкою на глибині, ціною втрати «перейти на сторінку 7», яка більшості реальних консьюмерів ніколи не була потрібна.

Переносне правило: пагінуйте за позицією в упорядкуванні, а не за лічбою від початку, щоразу, коли дані змінюються під читачами. Це маленьке дизайн-рішення з довгим хвостом: прикручувати cursor-и до вже відвантаженого offset-API — саме та міграція опублікованого контракту, про яку попереджала секція про версіонування.

Чи змінюється щось, коли консьюмер — AI-агент?

Принципи виживають; допуски жорсткішають. Агенти споживають API через tool definitions, і все перелічене вище стає машинно-орієнтованим: розпливчасте повідомлення про помилку, повз яке людина знизала б плечима, відправляє агента в retry-цикл, неконсистентний envelope ламає його парсинг, недодокументований параметр — галюцинується. Проєктувати API, якими агенти можуть користуватися надійно, — точні schema, вичерпні коди помилок, чесні описи — те саме ремесло, що й хороший REST-дизайн, тільки без знижки на недбалість. У цьому теза пояснення MCP: інтерфейси інструментів — це дизайн API, другий раунд, з менш поблажливим консьюмером.

Ось чому шлях фундаментальними курсами досі проходить через побудову реальних REST-сервісів — Node.js і production-API заради самого ремесла, на поверсі backend-архітектури, де вирішується, що сервіс, а що handler, з TypeScript як дисципліною типізації, яка робить контракти примусовими, а не декларативними. У кожної з цих навичок тепер є другий замовник: агент, що викликає ваш API через schema, — так само буквальний, як тайпчекер, і помітно дорожчий, коли збитий з пантелику.

Чек-лист виживання

Версія в URL з першого дня; після публікації — лише адитивні зміни. Deny-by-default auth; перевірка володіння на кожному читанні ресурсу. Один error envelope усюди: машинний код, людське повідомлення, correlation ID. Cursor-и, а не offset-и, всюди, де дані рухаються. Документуйте для найбуквальнішого консьюмера, який у вас коли-небудь буде, — бо з цього року він у вас є. Ніщо з цього не гламурне. Як і міст, що не падає.

Поділитися
X LinkedIn
Наступний крок

Закріпіть цю тему на курсі

Структурований шлях від теорії до production-коду — з проєктами та код-рев'ю.

Oleksii Anzhiiak

Автор статті

Oleksii Anzhiiak

Софтвер-архітектор, Senior .NET інженер та співзасновник

Олексій Анжіяк — софтвер-архітектор, Senior .NET інженер та співзасновник ToyCRM.com і ProfectusLab. Має понад 15 років досвіду у розподілених системах, хмарній інфраструктурі, high-load backend-розробці та identity-платформах. Проєктує складні архітектури, створює безпечні системи автентифікації та розробляє сучасні освітні програми, які допомагають студентам досягати реальних кар'єрних результатів.

LinkedIn

Рекомендуємо подивитися

Підібрані сторонні відео за темою. Відкриваються на YouTube.

~1:56:00
Просунутий Andrej Karpathy

Створюємо GPT з нуля

Рідкісний практичний розбір внутрішньої архітектури GPT — від теорії до коду.

~27:00
Середній 3Blue1Brown

Трансформери — технологія, що стоїть за LLM (Deep Learning, розділ 5)

Фірмове візуальне пояснення архітектури трансформера від 3Blue1Brown. Найкращий 30-хвилинний праймер для інженерів — спочатку інтуїція, потім математика.

~1:00:00
Початківець Andrej Karpathy

[Годинна доповідь] Вступ до Large Language Models

Годинний розбір від Карпатого: як насправді працюють LLM — inference, навчання, fine-tuning і LLM-OS, що формується. Найясніша єдина ментальна модель для інженерів-початківців.

Зв'язатися з нами