Поширені помилки у дизайні REST API

Зміст

У світі розробки ПЗ REST API вже давно став стандартом для взаємодії між клієнтами та серверами. Але навіть досвідчені розробники допускають помилки при дизайні REST API, які впливають на його продуктивність, безпеку та зручність у використанні. Давайте розглянемо основні проблеми, про які йшла мова в доповіді, а також рішення, що дозволять уникнути цих помилок.

Версіонування API. Як уникнути хаосу

Однією з найбільших проблем при роботі з REST API є версіонування. При неправильному підході до управління версіями ви ризикуєте створити хаос, який може призвести до значних проблем у підтримці сумісності та задоволенні клієнтів.

API версіюються, щоб зберігати стабільність, коли відбуваються зміни. Є три основних види версіонування:

  • Основна версія (Major): збільшується при внесенні змін, які порушують зворотну сумісність (breaking changes). Наприклад, зміна структури відповіді або URL ресурсу.

  • Мала версія (Minor): змінюється при додаванні нових можливостей, які зберігають зворотну сумісність, наприклад, додавання нового параметра до запиту.

  • Патч-версія (Patch): використовується для виправлення помилок або внесення невеликих покращень, що не впливають на загальну поведінку API.

Як показує практика, не завжди легко дотримуватися правил, особливо коли йдеться про зворотну сумісність. Розглянемо приклад: у вас є клієнт, який використовує стару версію вашого API. Якщо ви оновлюєте API, змінюючи структуру даних, клієнт може перестати працювати коректно. Наприклад, якщо ви додали новий обов'язковий параметр у відповідь, який старий клієнт не розуміє, він може просто "зламатися".

Порада: обмежте час підтримки кількох версій. Можливість надавати доступ до різних версій API на певний час дозволяє плавно переходити до нових версій, але не варто робити це на постійній основі. В іншому випадку ваш код швидко стане некерованим.

Небезпека експорту внутрішніх структур даних

Одна з найчастіших помилок при проектуванні REST API — використання внутрішніх доменних моделей як відповідей на запити. Це може призвести до витоку конфіденційної інформації та ускладнення внесення змін у майбутньому.

Розглянемо приклад. У вашій базі даних є сутність користувача, що включає такі поля, як username, email, passwordHash, createdAt, updatedAt. Якщо ви експортуєте цю сутність безпосередньо через API, то ви ризикуєте розкрити хеш паролю, дату створення і змінення, що може бути небажаним для клієнта або навіть небезпечним.

Для уникнення таких ситуацій використовуйте DTO (Data Transfer Object), що ізолює внутрішню логіку програми від клієнтського API. Таким чином, ви контролюєте, які дані будуть надіслані клієнту, і зменшуєте ризик витоку конфіденційної інформації. Використання DTO також дозволяє легко змінювати внутрішню модель, не впливаючи на зовнішній контракт API.

CQRS. Чи варто розділяти запити і команди

CQRS (Command Query Responsibility Segregation) — це підхід, який передбачає розділення моделей для читання та запису. Він дозволяє використовувати окремі об'єкти для запитів (читання) і команд (зміни).

Розглянемо ситуацію, коли одна й та сама модель даних використовується як для відображення інформації, так і для її створення чи оновлення. Наприклад, ви використовуєте ProductDTO для отримання даних про продукт і для створення нового продукту. Проблема полягає в тому, що не всі поля потрібні для створення: такі як id або createdAt генеруються на сервері, тому клієнт не має їх передавати. Якщо ж використовується одна і та сама модель, це може призвести до плутанини та небажаних помилок.

CQRS допомагає уникнути таких ситуацій, розділяючи об'єкти на ProductCreateRequest і ProductResponse. Так ви чітко розділяєте, що використовується для створення, а що для відповіді.

Але не поспішайте впроваджувати CQRS на всіх рівнях. CQRS на рівні бази даних, що передбачає розділення репозиторіїв для читання і запису, може суттєво ускладнити систему. Розподілена CQRS підходить лише для дуже великих і складних систем, інакше витрати на її підтримку перевищать вигоди.

Мікросервіси та проблема посередника

У доповіді згадувалася цікава проблема, яку часто не помічають — патерн "людина в середині" ("man-in-the-middle" pattern), коли один мікросервіс просто переадресовує запит іншого сервісу.

Розглянемо приклад: сервіс A викликає сервіс B, а сервіс B передає запит на сервіс C і повертає результат назад. Якщо сервіс B не додає ніякої логіки, це просто створює зайву ланку. У такому випадку краще дозволити сервісу A напряму викликати сервіс C. Це знижує затримки і спрощує архітектуру.

Чистий контракт API через часткові запити

Існує практика, яка часто стає причиною проблем з консистентністю даних — використання PUT для повернення оновлених даних. Наприклад, після виконання PUT-запиту багато хто повертає об'єкт з новими даними. Але це може призвести до проблем, коли система працює у високонавантаженому середовищі або виникають складності з розподіленими транзакціями.

Рішення: розділіть логіку команд і читання. Використовуйте PUT для змін, а потім окремий GET для отримання оновлених даних. Це забезпечує більшу чіткість контракту API та запобігає можливим помилкам з боку клієнтів.

Деталізація API. Уникнення надмірної кількості даних в одному запиті

Часто інтерфейси користувача проектуються таким чином, що в одному запиті відображається забагато інформації. Наприклад, форма редагування товару може включати всі атрибути, включаючи активність, ціну, наявність і опис. Це може призвести до проблем із конкурентним доступом.

Уявіть, що менеджер редагує опис продукту, а в цей же час інший менеджер змінює його статус на "неактивний". Якщо обидва запити PUT будуть оброблені одночасно, можна отримати непередбачуваний результат.

Замість цього розробіть інтерфейс, який відокремлює різні частини об'єкта і дозволяє редагувати лише ті частини, які актуальні для конкретного користувача. Наприклад, створіть окремі ендпоінти для редагування опису, оновлення ціни та зміни статусу.

Використання семантичних назв для дій в API

REST API зазвичай передбачає використання HTTP-методів (GET, POST, PUT, DELETE) для роботи з ресурсами, які є іменниками (наприклад, /products). Проте іноді необхідно використовувати дії замість ресурсів. Наприклад, ендпоінт /products/{id}/deactivate дає чітке розуміння, що ця дія означає деактивацію продукту.

Це не відповідає "чистому" підходу REST, але робить API більш зрозумілим і зручним для кінцевих користувачів. Використовуйте стандартний REST підхід до тих пір, поки це можливо, але не бійтеся відступити від нього, якщо це зробить API більш зрозумілим.

Часткові оновлення та проблема Patch

Оновлення даних через метод PATCH також може бути проблематичним, якщо його використовувати неправильно. PATCH призначений для часткових оновлень ресурсу, але часто виникають складнощі з правильним визначенням змін.

Наприклад, якщо у вас є складна структура JSON і ви хочете змінити лише одне поле, важливо забезпечити правильну валідацію всіх полів, щоб уникнути небажаних змін. В іншому випадку PATCH може створити незручності при відстеженні історії змін або уніфікації логіки обробки запитів.

Аналітика та спостереження за API

Для уникнення проблем із версіонуванням та сумісністю важливо розуміти, як клієнти використовують ваш API. Використовуйте аналітичні інструменти для відстеження, хто і як використовує кожну версію вашого API. Це дозволить вам зважено планувати припинення підтримки старих версій та запобігати раптовим помилкам у клієнтів.

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

Висновок

Правильний дизайн REST API є ключовим фактором для забезпечення ефективності, безпеки та зручності використання ваших сервісів. Дотримуючись рекомендацій, розглянутих у цій статті, можна уникнути багатьох поширених помилок:

  • Версіонування має бути структурованим і обмеженим у часі для підтримки.

  • Використовуйте DTO для ізоляції внутрішніх даних і зовнішнього контракту API.

  • CQRS застосовуйте для складних систем, але не перебільшуйте з його впровадженням.

  • Використовуйте семантично зрозумілі дії, коли REST обмежує гнучкість.

  • Завжди відокремлюйте читання та запис у вашому API для забезпечення консистентності.

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


Довгочит — короткий текстовий опис доповіді Віктора про помилки в проектуванні REST API систем. Раджу подивитися — дуже цікаво та інтерактивненько.

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Oleksandr Klymenko
Oleksandr Klymenko@overpathz

Java Software Engineer

4.3KПрочитань
1Автори
71Читачі
На Друкарні з 19 квітня

Більше від автора

  • Java. Короткий огляд еволюції багатопотоковості

    У перших версіях Java багатопоточність реалізовувалася за допомогою класу Thread, який дозволяв створювати нові потоки. Проте ця модель мала багато недоліків:

    Теми цього довгочиту:

    Java
  • 10 “маловідомих” концепцій тестування продуктивності

    Довгочит висвітлює 10 маловідомих концепцій тестування продуктивності. Описано їхнє значення, причини виникнення, методи вирішення та моніторингу, що допомагає оптимізувати роботу систем.

    Теми цього довгочиту:

    Programming
  • 🕵️ Автентифікація без пароля?

    У світі, де кібербезпека стає все більш важливою, традиційні методи автентифікації, такі як паролі, вже не відповідають сучасним вимогам безпеки. На допомогу приходить безпарольна автентифікація, зокрема стандарти FIDO2 та WebAuthn.

    Теми цього довгочиту:

    Security

Вам також сподобається

Коментарі (0)

Підтримайте автора першим.
Напишіть коментар!

Вам також сподобається