Ви заробляєте на інформації (навіщо потрібен API і як його грамотно спроектувати)

Доброго дня, мене звуть Олександр Зеленін і я веб-розробник.
Інформація — основа будь-якого додатку або сервісу.



Більше 10 років тому я спілкувався з власником покер-руму, і він показав мені сторінку, яка приносила близько 10 000$ в день. Це була зовсім банально оформлена сторінка. На ній не було ні стилю, ні графіки. Суцільний текст, розбитий заголовками розділів і посиланнями. У мене просто не вкладалося в голові — ну як ось це може приносити такі гроші?

Секрет у тому, що «ось це» було одним з перших вичерпних інструкцій з гри в покер онлайн. У сторінки було PageRank 10/10 (або 9, не суть), і в пошуковій видачі це було перше, на що натикалися.

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

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

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

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

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

Музичний стриминговый сервіс — мета-інформація + музичні файлиКористувач хоче знайти інформацію, що його музику. Всі обгортки, розумні черги, ліцензійність та інша лушпиння мало кого цікавить.

Звичайно, добре використовувати ліцензійний контент, але якщо користувач не може знайти те, що шукав — він піде і знайде це в іншому місці. В інтернеті люди не запам'ятовують інформацію як таку, вони запам'ятовують місце, де цю інформацію знайшли. Тому, якщо на вашому сайті немає пісень групи Х, але зате є посилання на сторінку групи Х, де вони продають свої альбоми, ваш сервіс все одно в плюсі, тому що користувач запам'ятав, де він взяв інформацію про групу Х і повернеться до вас ще раз пошукати інформацію про групу Y.

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

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


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

Так ось: чим би ви не займалися, первинної завжди буде інформація. Хорошу, якісну інформацію користувачі обов'язково знайдуть і звернуться до вас.

Я розповім, як організувати роботу з інформацією так, щоб це було:
1. Масштабируемо — реплікація, шардирование і т. п. настроюється БЕЗ втручання в роботу програми.
2. Зручно для користувачів — легко документувати, зрозуміло як використовувати.
3. Зручно для ваших розробників — швидке прототипування, можливості оптимізації тільки необхідного.

Даний підхід не має сенсу для вас, якщо у вас маленький проект з невеликою кількістю компонентів і розробників.



Зміст:
  1. Споживачі інформації
  2. Як працювати з інформацією (API)
  3. Внутрішній шар API
  4. Зовнішній шар API
  5. Оптимізація важких запитів
  6. Користувачі
  7. Масштабування
  8. Кешування
  9. Версіонування
  10. Підсумок


Допущення / нестача інформаціїУ тексті статті я використовую ряд припущень: наприклад, що у вас вже є або реалізовано.

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

Якщо вважаєте, що все ж чогось не вистачає — ласка, повідомте, і стаття буде доповнено згідно з побажаннями.



Споживачі інформації
Споживачів інформації можна розділити на дві категорії — внутрішні і зовнішні.

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

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


Як працювати з інформацією?
Для роботи з інформацією ми будемо надавати web API. Реалізовувати будемо 2 шари (зовнішній і внутрішній). Шар передбачає, як мінімум, окремий додаток.

Навіщо потрібен API?

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

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

В першу чергу необхідно описати моделі і колекції даних. У разі якщо додаток реалізується на Javascript (nodejs на сервері), з'явиться можливість використовувати одні і ті ж моделі і на клієнтах, і на серверах.

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

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

В якості прикладу я наведу код, що описує якийсь музичний сервіс.

Приклади будуть на Javascript, проте все описане застосовне до будь-якої мови. Я робив подібні речі також на php, python і c++. Все необхідно варіювати в залежності від розмірів проекту.


Model.extend('Track', { // Назва моделі
attributes: {
id: 'integer', // Поле та його тип
title: 'string',
url: 'string', // Посилання/шлях до файлу
duration: 'integer',

album: 'app.model.Album.model', // Зв'язок з іншими моделями
artist: 'app.model.Artist.model'
}
})


Перевірка коректності даних (валідація)Щоб не захаращувати код, я опущу докладні описи перевірок в прикладах. При бажанні можна вказувати будь-яку кількість критеріїв, тексти помилок валідації і т. п. Валідація застосовна як на клієнтах, так і на серверах.

Один приклад:
Model.extend('Track', {
attributes: {
...
title: {
type: {
value: 'string',
errorText: 'Повинне мати тип «Рядок»'
},
required: {
value: true,
errorText: 'Поле обов'язкове для заповнення'
},
length: {
min: 5,
max: 32,
errorText: 'Довжина поля повинна бути від 5 до 32 символів'
}
}
...
}
})



Колекція — набір сутностей (зазвичай однакової природи, тобто, наприклад, музичні треки). Набір даних також може містити додаткові, що відносяться до самого набору даних. Як метаінформації може бути представлено кількість обраних треків, кількість (не обраних) треків, номер сторінки, кількість сторінок. Віртуальним полем може бути загальна тривалість всіх треків.

Model.List.extend('Track.List', { // Назва колекції
attributes: {
duration: 'virtual' // Віртуальне поле, обчислюється в момент запиту
}
}, {
duration: function(tracks) { // Варіант реалізації віртуального поля
return _.reduce(tracks, function(totalDuration, track) {
return (track.duration || 0) + totalDuration;
})
}
})



Внутрішній шар API
Цей шар буде доступний тільки нашим продуктам.

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

Розширення моделейРозширюємо модель на сервері і клієнті, описуючи назву і шлях. Загальні реалізації методів findOne, update, destroy і create описані в абстракції моделі і не вимагають окремої реалізації, якщо принципово не відрізняються.
Model.extend('Track', { // 
findOne: 'GET /track/{id}', // Знайти один трек по унікальному ідентифікатору
update: 'PUT /track/{id}', // Оновити змінені поля
destroy: 'DELETE /track/{id}', // Видалити з бази
create: 'POST /track' // Додати новий трек
})


Розширюємо модель тільки на сервері:
Model.extend('Track', {
'GET /track/top/today': function() { // Повертає кращий трек за сьогодні
var track = ...;
...
return track;
}
})


Розширюємо модель тільки на внутрішньому клієнта:
Model.extend('Track', {
findTodayTop: 'GET /track/top/today'
})


Model.extend('Track.List', {
findByArtistId: 'GET /track/byArtistId/{artist_id}' // Знайти усі треки, що належать вказаною артистові
})



На цьому шарі ми маємо максимальну гнучкість запитів.

Приклад запиту та відповіді
app.model.Track.List.findByArtistId({
format: 'json',
artist_id: 20974,
fields: [ // Поля, які хочемо отримати в запиті
track, // Всі поля основної моделі
track.artist, // Всі поля пов'язаного музиканта
track.album.name // Назву альбому, в який входить цей трек. Без інших полів.
],
offset: 5, // Пропускаємо перших 5 записів
limit: 10, // Отримуємо не більше 10 записів
sort: [
'track.title': 'ASC' // Сортуємо треки в алфавітному порядку
],
cache: 1800 // Кешуємо на стороні клієнта на півгодини
})


У відповідь отримуємо щось типу:

{
"result": {
"tracks": [
...,
{
"id": 856, // Всі власні поля моделі
"title": "Чудовий трек",
"url": "/який/то/шлях/до/треку",
"duration": 216,
"artist": {
"id": 20974,
"title": "Чудовий музикант",
... // інші поля
},
"album": {
"name": "Чудовий альбом" // тільки ім'я
}
},
...
]
},
"offset": 5, // Відступ
"count": 7, // Вибрано зараз
"totalCount": 12 // Всього знайдено
}


Не обов'язково саме так вкладати інформацію. Якщо боїтеся дублікатів (хоча, якщо в плані трафіку, то gzip з ними відмінно справляється), можна збирати в окремі поля в початковий result.



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

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

Почасти він є проксі до внутрішнього API з низкою важливих доповнень.

Відразу приклад:
app.get('/api/track/:id', ..., function(req, res) {
return app.model.Track.findOne({id: req.params.id});
})


Замість «...» робимо необхідні перевірки прав, модифікуємо запит, визначаємо формат. Дані повертаються у тому ж форматі і тим же шляхом, як були запитані.
Тобто для http та json дані так і повернуться. Для socket і xml відповідь буде через сокет і xml.

Таким чином ми можемо повністю контролювати, що доступно ззовні, як, на яких умовах і так далі.


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

Припустимо, ми звернули увагу, що повільно працює запит, в якому вибирається трек разом з інформацією про альбом і музиканта. Для оптимізації нам знадобиться створити новий внутрішній метод:
Model.extend('Track', {
'GET /track/{id}/withAdditionalData': function() {
var track = ...;
// Тут вибираємо дані максимально оптимальним способом
// також використовуємо можливості кеш
return track;
}
})

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


Користувачі
Основне завдання при роботі з користувачами — перевіряти їх права.

Як тільки користувач авторизується (спосіб не важливий: куки, ключ, інший варіант), ми робимо один запит до внутрішнього шару, засвідчуючи особу і отримуючи дозволи. Далі перевірки ми будемо робити на зовнішньому шарі.


Масштабування
Великі переваги ми отримуємо саме на етапі масштабування.

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

Бази даних розширюються класичними способами, що залежать від завдання.


Кешування
Для внутрішнього шару ми кешуємо результати запитів до баз даних, а на зовнішньому — результати, отримані від внутрішнього шару. У кінцевого клієнта теж може бути кешування.

В одному з попередніх прикладів була рядок «cache: 1800» — вона може надати кеш як на зовнішньому шарі, запам'ятовуючи результат на сервері на півгодини, так і на клієнті, склавши результат, наприклад в localStorage (ну або інше сховище клієнта).


Версіонування
З розвитком вашого проекту будуть з'являтися нові методи, а які-небудь старі — йти. Для вказівки версій я рекомендую, безсумнівно, Семантичне Версіонування. Нас особливо цікавить зміна мажорній версії API, без зворотної сумісності. Шляхи доступу до API можна розділити просто: /api/{version}

Організувати файли і підтримку різних версій можна кількома шляхами, наприклад:
1. Зробити папки v1, v2 і помістити в них весь відноситься до них код. При модифікації однією з версій API інша не зачіпається.
2. Різні репозиторії, функціонують як різні додатки.


Підсумок



  1. Ми маємо повний контроль над рухом даних на всіх етапах.
  2. Розробники задоволені, їм не треба чекати від команди API реалізації суперхитрого методу для отримання даних у певному форматі.
  3. Розробники задоволені, вони можуть оптимізувати тільки те, що гальмує.
  4. Клієнти задоволені — API стабільніше і не змінює шляху з-за того, що якийсь запит був гальмівний.
  5. Клієнти задоволені — швидкість доступу збільшується за рахунок розташування сервера максимально близько.


Буду радий додати в статтю додаткові розділи за вашим запитом.
Також, якщо хочете уточнити десь код — напишу і додам, запитуйте.
Можливо, це початок великого циклу статей на різні теми (або початок вже покладено). Матеріалу накопичилося дуже багато, але він не систематизований.

Офтопік питання про створення курсів для бажаючих розробляти веб проектиЯ хочу створити повноцінний курс по розробці веб проектів. Прямо з нуля і до full-stack.

За планами він буде включати: відеолекції + текстові уроки, домашні завдання, самостійні проекти, роботу з ментором, різні інтенсиви, купу коду (у форматі запуску/доопрацювання онлайн) і так далі. В якості особливості хочу зробити «мережевий» формат, а не послідовний. Тобто після проходження певних тем відкриваються інші, і учень може сам вибирати, що його цікавить в даний момент. За попередніми оцінками тривалість навчання буде в районі півроку повноцінних занять по парі годин у день.

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

Я вже писав ряду проектів типу coursera з описом своєї затії, але не отримав жодної відповіді. Причому, що прикро, взагалі ніяких, навіть відмов.

Ринок вивчав і впевнений, що те, що реалізую, більш ніж конкурентними.

Буду радий будь-яким пропозиціям про партнерство чи порад, до кого слід звернутися.


Джерело: Хабрахабр

0 коментарів

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