Думи про web-API. частина перша

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

Чтиво вийшло більше філософське, ніж технічний, але і для любителів технічної частини тут буде над чим поміркувати. Сумніваюся, що скажу в цій статті щось принципово нове, те, про що ви ніколи не чули, не читали й не думали самі. Просто спробую викласти все в єдину систему, в першу чергу у своїй власній голові, а це вже дорогого коштує. Тим не менш, буду радий, якщо мої вигадки будуть вам корисні у вашій практиці. Отже, поїхали.

перше Наближення: Дійові особи
Клієнт і сервер
Сервером в даному випадку ми вважаємо абстрактну машину в мережі, здатну отримати HTTP-запит, обробити його і повернути коректну відповідь. У контексті даної статті абсолютно не важливі його фізична суть та внутрішня архітектура, будь то студентський ноутбук або величезний кластер з промислових серверів, розкиданих по всьому світу. Нам в тій же мірі абсолютно неважливо, що у нього під капотом, хто зустрічає запит біля дверей, Apache або Nginx, який невідомий звір, PHP, Python або Ruby виконує його обробку і формує відповідь, яке сховище даних використовується: Postgresql, MySQL або MongoDB. Головне, щоб сервер відповідав головному правилу – почути, зрозуміти і пробачити відповісти.

Клієнтом може бути все, що завгодно, що здатне сформувати і відправити HTTP-запит. До певного моменту в цій статті нам також не будуть особливо цікаві цілі, які ставить перед собою клієнт, відправляючи цей запит, як і те, що він буде робити з відповіддю. Клієнтом може бути JavaScript-сценарій, який працює в браузері, мобільний додаток, злий (або не дуже) демон, запущений на сервері, або занадто поумневший холодильник (вже є і такі).

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

Філософія REST
REST (Representational state transfer) спочатку був задуманий як простий і однозначний інтерфейс для управління даними, що передбачав лише кілька базових операцій з безпосереднім мережевим сховищем (сервером): витяг даних (GET), збереження (POST), зміна (PUT/PATCH) і видалення (DELETE). Зрозуміло, цей перелік завжди супроводжувався такими опціями, як обробка помилок у запиті (чи коректно складено запит), розмежування доступу до даних (раптом цього вам знати не слід) і валідація вхідних даних (раптом ви написали дурницю), загалом, усіма можливими перевірками, які сервер виконує перед тим, як виконати бажання клієнта.

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

Незалежність сервера від клієнта – сервери і клієнти можуть бути миттєво замінені іншими незалежно один від одного, так як інтерфейс між ними не змінюється. Сервер не зберігає станів клієнта.

Унікальність адрес ресурсів – кожна одиниця даних (будь-якого ступеня вкладеності) має свій власний унікальний URL, який, по суті, є цілком однозначним ідентифікатором ресурсу.

Приклад: GET /api/v1/users/25/name

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

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

Чого нам не вистачає
Класичний REST передбачає роботу клієнта з сервером як з плоским сховищем даних, при цьому нічого не говориться про пов'язаності та взаємозалежності між собою. Все це за замовчуванням цілком лягає на плечі клієнтського додатку. Однак сучасні предметні області, для яких розробляються системи управління даними, чи то соціальні сервіси або системи інтернет-маркетингу, мають на увазі складну взаємозв'язок між сутностями, що зберігаються в базі даних. Підтримка цих зв'язків, тобто цілісності даних, знаходиться в зоні відповідальності серверної сторони, в той час, як клієнт є тільки інтерфейсом для доступу до цих даних. Так чого ж нам не вистачає в REST?

Виклики функцій

Необхідність виділення функцій як окремого способу управління даними продиктована тим, що набирають чинності принципи інкапсуляції методів, атомарности транзакцій і підтримки цілісності даних. Щоб не змінювати дані і зв'язку між ними вручну, ми викликаємо у ресурсу (одиничного об'єкта, або колекції) функцію і «згодовуємо» їй як аргумент необхідні параметри. Ця операція не підходить під стандарти REST, для неї не існує особливого дієслова, як і способу вказівки імені функції, що змушує нас, розробників, викручуватися хто на що здатний.

найпростіший приклад – авторизація користувача. Ми викликаємо функцію POST /api/v1/auth/login, передаємо їй як аргумент об'єкт, що містить облікові дані, і у відповідь отримуємо ключ доступу. Що коїться з даними на серверній стороні – нас не хвилює.

варіант – створення і розрив зв'язків між сутностями. Наприклад, додавання користувача в групу. Викликаємо у сутності група функцію POST /api/v1/groups/1/addUser, в якості параметра передаємо об'єкт користувач, отримуємо результат. Приклад, зрозуміло, притягнутий за вуха, але частенько при створенні зв'язків можливі додаткові операції з даними на серверній стороні.

А ще бувають операції, які взагалі не пов'язані безпосередньо з збереженням даних як таких, наприклад, розсилання повідомлень, підтвердження або відхилення будь-яких операцій (завершення звітного періоду etc).

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

Множинні операції

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

Для web-api, що прагне до ідеалу, теж хотілося б якось привести подібні операції в систему. Постараюся зробити це в одному з продовжень.

Статистичні запити, агрегатори, форматування даних

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

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

Різновиди даних

Об'єкти

Ключовим типом даних у спілкуванні між клієнтом і сервером виступає об'єкт. По суті, об'єкт – це перелік властивостей і відповідних їм значень. Ми можемо відправити об'єкт на сервер запит і отримати результат запиту у вигляді об'єкта. При цьому об'єкт не обов'язково буде реальною сутністю, що зберігається в базі даних, принаймні, в тому вигляді, в якому він відправлений або одержаний. Наприклад, облікові дані для авторизації передаються у вигляді об'єкта, але не є самостійною сутністю. Навіть зберігаються в БД об'єкти схильні обростати " додатковими властивостями внутрішньосистемного характеру, наприклад, датами створення і редагування, різними системними мітками і прапорами. Властивості об'єктів можуть бути як власними скалярними значеннями, так і містити пов'язані об'єкти і колекції об'єктів, які не є частиною об'єкта. Частина властивостей об'єктів може бути редагованої, частина системної, доступною тільки для читання, а частина може носити статистичний характер і обчислюватися на льоту (наприклад, кількість лайків). Деякі властивості об'єкта можуть бути приховані, в залежності від прав користувача.

Колекції об'єктів

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

Скалярні значення

У чистому вигляді скалярні значення як окрема сутність на моїй пам'яті зустрічалися вкрай рідко. Зазвичай вони фігурували як властивості об'єктів чи колекцій, і в цій якості вони можуть бути доступні як для читання, так і для запису. Наприклад, ім'я користувача може бути отримано і змінено в індивідуальному порядку GET /api/v1/users/1/name. На практиці ця можливість знадобиться рідко, але у разі необхідності хотілося б, щоб вона була під рукою. Особливо це стосується властивостей колекції, наприклад числа записів (з фільтрацією або без неї): GET /api/v1/news/count.

Файли

Файли файли — їх варто розглядати як єдину неподільну одиницю. Інше питання в тому, що в більшості випадків при збереженні файлу в базі може створюватися службова сутність, містить метадані файлу: розмір, справжнє ім'я, статус etc.

Продовження слідує...
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.