Вашата компания току-що стартира своя API и сега иска да изгради общност от потребители около нея. Знаете, че повечето от клиентите ви ще работят в JavaScript , тъй като услугите, които предоставя вашият API, улесняват клиентите да създават уеб приложения, вместо да пишат всичко сами - Twilio е добър пример за това.
Вие също знаете, че толкова просто, колкото и вашето RESTful API може би, потребителите ще искат да пуснат JavaScript пакет които ще направят цялото вдигане за тях. Те няма да искат да научат вашия API и сами да изградят всяка заявка, от която се нуждаят.
Така че изграждате библиотека около вашия API. Или може би просто пишете система за управление на състоянието за уеб приложение, което взаимодейства със собствения ви вътрешен API.
Така или иначе, не искате да се повтаряте отново и отново всеки път, когато CRUD някой от вашите API ресурси, или по-лошо, CRUD ресурс, свързан с тези ресурси. Това не е добре за управление на нарастващ SDK в дългосрочен план, нито е добро използване на вашето време.
Вместо това можете да използвате ActiveResource.js , JavaScript ORM система за взаимодействие с API. Създадох го, за да отговоря на нуждите, които имахме по проект: да създадем JavaScript SDK в възможно най-малко редове. Това даде възможност за максимална ефективност за нас и за нашата общност на разработчици.
Той се основава на принципите, стоящи зад простия ActiveRecord ORM на Ruby on Rails.
Има две идеи за Ruby on Rails, които ръководят дизайна на ActiveResource.js:
Product
ресурс, той съответства на /products
крайна точка. По този начин времето не се изразходва многократно за конфигуриране на всяка от заявките на вашия SDK към вашия API. Разработчиците могат да добавят нови API ресурси със сложни CRUD заявки към вашия нарастващ SDK за минути, а не за часове.Важно е да се отбележи, че по време на настоящото писане ActiveResource.js работи само с API, написани според JSON: API стандарт .
Ако не сте запознати с JSON: API и искате да продължите, има много добри библиотеки за създаване на JSON: API сървър.
Въпреки това ActiveResource.js е по-скоро DSL, отколкото обвивка за един конкретен API стандарт. Интерфейсът, който използва за взаимодействие с вашия API, може да бъде разширен, така че бъдещите статии могат да обхващат как да използвате ActiveResource.js с вашия персонализиран API.
За начало инсталирайте active-resource
във вашия проект:
yarn add active-resource
Първата стъпка е да създадете ResourceLibrary
за вашия API. Ще поставя всичките си ActiveResource
в src/resources
папка:
// /src/resources/library.js import { createResourceLibrary } from 'active-resource'; const library = createResourceLibrary('http://example.com/api/v1'); export default library;
Единственият задължителен параметър за createResourceLibrary
е основният URL на вашия API.
php-utf-8
Ще създадем JavaScript SDK библиотека за API на системата за управление на съдържанието. Това означава, че ще има потребители, публикации, коментари и известия.
Потребителите ще могат да четат, създават и редактират публикации; четете, добавяйте и изтривайте коментари (към публикации или към други коментари) и получавайте известия за нови публикации и коментари.
Няма да използвам конкретна библиотека за управление на изгледа (React, Angular и др.) Или състоянието (Redux и др.), Вместо да абстрахирам урока, за да взаимодействам само с вашия API чрез ActiveResource
s.
Ще започнем, като създадем User
ресурс за управление на потребителите на CMS.
Първо, ние създаваме User
ресурсен клас с някои attributes
:
// /src/resources/User.js import library from './library'; class User extends library.Base { static define() { this.attributes('email', 'userName', 'admin'); } } export default library.createResource(User);
Да приемем засега, че имате крайна точка за удостоверяване, която след като потребителят изпрати имейла и паролата си, връща токен за достъп и идентификатора на потребителя. Тази крайна точка се управлява от някаква функция requestToken
. След като получите удостоверения потребителски идентификатор, искате да заредите всички данни на потребителя:
import library from '/src/resources/library'; import User from '/src/resources/User'; async function authenticate(email, password) { let [accessToken, userId] = requestToken(email, password); library.headers = { Authorization: 'Bearer ' + accessToken }; return await User.find(userId); }
Зададох library.headers
да има Authorization
заглавка с accessToken
така че всички бъдещи заявки от моя ResourceLibrary
са упълномощени.
По-късен раздел ще обхване как да удостоверите потребител и да зададете маркера за достъп, използвайки само User
ресурсен клас.
Последната стъпка от authenticate
е заявка към User.find(id)
. Това ще направи заявка до /api/v1/users/:id
и отговорът може да изглежда по следния начин:
{ 'data': { 'type': 'users', 'id': '1', 'attributes': { 'email': ' [email protected] ', 'user_name': 'user1', 'admin': false } } }
Отговорът от authenticate
ще бъде екземпляр на User
клас. Оттук можете да получите достъп до различните атрибути на удостоверения потребител, ако искате да ги покажете някъде в приложението.
let user = authenticate(email, password); console.log(user.id) // '1' console.log(user.userName) // user1 console.log(user.email) // [email protected] console.log(user.attributes()) /* { email: ' [email protected] ', userName: 'user1', admin: false } */
Всяко от имената на атрибутите ще стане camelCased, за да отговаря на типичните стандарти на JavaScript. Можете да получите всеки от тях директно като свойства на user
обект или вземете всички атрибути, като извикате user.attributes()
.
какво е паралелност в програмирането
Преди да добавим още ресурси, свързани с User
клас, като известия, трябва да добавим файл, src/resources/index.js
, който ще индексира всички наши ресурси. Това има две предимства:
src/resources
за множество ресурси в един оператор за импортиране, вместо да се използват множество оператори за импортиране.ResourceLibrary
ние ще създадем, като извикаме library.createResource
на всеки, което е необходимо на ActiveResource.js за изграждане на взаимоотношения.// /src/resources/index.js import User from './User'; export { User };
Сега нека създадем свързан ресурс за User
, | Notification
. Първо създайте Notification
клас, който belongsTo
User
клас:
// /src/resources/Notification.js import library from './library'; class Notification extends library.Base { static define() { this.belongsTo('user'); } } export default library.createResource(Notification);
След това го добавяме към индекса за ресурси:
// /src/resources/index.js import Notification from './Notification'; import User from './User'; export { Notification, User };
След това свържете известията с User
клас:
// /src/resources/User.js class User extends library.Base { static define() { /* ... */ this.hasMany('notifications'); } }
След като върнем потребителя от authenticate
, можем да заредим и покажем всички негови известия:
let notifications = await user.notifications().load(); console.log(notifications.map(notification => notification.message));
Също така можем да включим известия в оригиналната ни заявка за удостоверения потребител:
async function authenticate(email, password) { /* ... */ return await User.includes('notifications').find(userId); }
Това е една от многото опции, налични в DSL.
Нека разгледаме това, което вече е възможно да поискаме само от кода, който сме написали досега.
Можете да заявите колекция от потребители или един потребител.
let users = await User.all(); let user = await User.first(); user = await User.last(); user = await User.find('1'); user = await User.findBy({ userName: 'user1' });
Можете да модифицирате заявката, като използвате възможни релационни методи:
// Query and iterate over all users User.each((user) => console.log(user)); // Include related resources let users = await User.includes('notifications').all(); // Only respond with user emails as the attributes users = await User.select('email').all(); // Order users by attribute users = await User.order({ email: 'desc' }).all(); // Paginate users let usersPage = await User.page(2).perPage(5).all(); // Filter users by attribute users = await User.where({ admin: true }).all(); users = await User .includes('notifications') .select('email', { notifications: ['message', 'createdAt'] }) .order({ email: 'desc' }) .where({ admin: false }) .perPage(10) .page(3) .all(); let user = await User .includes('notification') .select('email') .first();
Забележете, че можете да съставите заявката, като използвате произволно количество верижни модификатори и че можете да завършите заявката с .all()
, .first()
, .last()
или .each()
.
Можете да изградите потребител локално или да го създадете на сървъра:
let user = User.build(attributes); user = await User.create(attributes);
След като имате постоянен потребител, можете да изпратите промени в него, за да бъдат запазени на сървъра:
user.email = ' [email protected] '; await user.save(); /* or */ await user.update({ email: ' [email protected] ' });
Можете също да го изтриете от сървъра:
await user.destroy();
Този основен DSL се разпростира и върху свързани ресурси, както ще демонстрирам в останалата част от урока. Сега можем бързо да приложим ActiveResource.js за създаване на останалата част от CMS: публикации и коментари.
Създайте клас ресурс за Post
и го свържете с User
клас:
// /src/resources/Post.js import library from './library'; class Post extends library.Base { static define() { this.belongsTo('user'); } } export default library.createResource(Post);
// /src/resources/User.js class User extends library.Base { static define() { /* ... */ this.hasMany('notifications'); this.hasMany('posts'); } }
Добавете Post
към индекса на ресурсите също:
// /src/resources/index.js import Notification from './Notification'; import Post from './Post'; import User from './User'; export { Notification, Post, User };
След това завържете Post
ресурс във форма за потребителите да създават и редактират публикации. Когато потребителят за първи път посети формуляра за създаване на нова публикация, Post
ресурс ще бъде изграден и всеки път, когато формата се променя, ние прилагаме промяната към Post
:
import Post from '/src/resources/Post'; let post = Post.build({ user: authenticatedUser }); onChange = (event) => { post.content = event.target.value; };
След това добавете onSubmit
обратно извикване към формуляра, за да запишете публикацията на сървъра и да обработвате грешки, ако опитът за запис е неуспешен:
медийна заявка за отзивчив дизайн
onSubmit = async () => { try { await post.save(); /* successful, redirect to edit post form */ } catch { post.errors().each((field, error) => { console.log(field, error.message) }); } }
След като публикацията бъде запазена, тя ще бъде свързана с вашия API като ресурс на вашия сървър. Можете да разберете дали ресурсът се запазва на сървъра, като извикате persisted
:
if (post.persisted()) { /* post is on server */ }
За персистиращи ресурси ActiveResource.js поддържа мръсни атрибути, тъй като можете да проверите дали някой атрибут на ресурс е променен от стойността му на сървъра.
Ако се обадите save()
на персистиращ ресурс, той ще направи PATCH
заявка, съдържаща само направените промени в ресурса, вместо да изпраща изцяло набора от атрибути и връзки на ресурса към сървъра.
Можете да добавяте проследени атрибути към ресурс, използвайки attributes
декларация. Нека проследим промените в post.content
:
// /src/resources/Post.js class Post extends library.Base { static define() { this.attributes('content'); /* ... */ } }
Сега, с поддържана от сървъра публикация, можем да редактираме публикацията и когато се кликне върху бутона за изпращане, запазете промените на сървъра. Също така можем да деактивираме бутона за изпращане, ако все още не са направени промени:
onEdit = (event) => { post.content = event.target.value; } onSubmit = async () => { try { await post.save(); } catch { /* display edit errors */ } } disableSubmitButton = () => { return !post.changed(); }
Има методи за управление на единична връзка като post.user()
, ако искахме да променим потребителя, свързан с публикация:
await post.updateUser(user);
Това е еквивалентно на:
await post.update({ user });
Сега създайте ресурсен клас Comment
и го свържете с Post
. Не забравяйте нашето изискване коментарите да бъдат в отговор на публикация или друг коментар, така че съответният ресурс за коментар е полиморфен:
// /src/resources/Comment.js import library from './library'; class Comment extends library.Base { static define() { this.attributes('content'); this.belongsTo('resource', { polymorphic: true, inverseOf: 'replies' }); this.belongsTo('user'); this.hasMany('replies', { as: 'resource', className: 'Comment', inverseOf: 'resource' }); } } export default library.createResource(Comment);
Не забравяйте да добавите Comment
до /src/resources/index.js
както добре.
Ще трябва да добавим ред към Post
клас също:
// /src/resources/Post.js class Post extends library.Base { static define() { /* ... */ this.hasMany('replies', { as: 'resource', className: 'Comment', inverseOf: 'resource' }); } }
inverseOf
опцията е предадена на hasMany
определение за replies
показва, че тази връзка е обратна на полиморфната belongsTo
определение за resource
. inverseOf
свойството на връзките често се използват при извършване на операции между връзки. Обикновено това свойство ще се определя автоматично чрез името на класа, но тъй като полиморфните връзки могат да бъдат един от множество класове, трябва да дефинирате inverseOf
изберете себе си, за да могат полиморфните връзки да имат същата функционалност като нормалните.
Същият DSL, който се прилага за ресурси, се отнася и за управлението на свързани ресурси. След като създадохме връзките между публикации и коментари, има редица начини да управляваме тази връзка.
Можете да добавите нов коментар към публикация:
onSubmitComment = async (event) => { let comment = await post.replies().create({ content: event.target.value, user: user }); }
Можете да добавите отговор към коментар:
onSubmitReply = async (event) => { let reply = await comment.replies().create({ content: event.target.value, user: user }); }
Можете да редактирате коментар:
onEditComment = async (event) => { await comment.update({ content: event.target.value }); }
Можете да премахнете коментар от публикация:
onDeleteComment = async (comment) => { await post.replies().delete(comment); }
SDK може да се използва за показване на страничен списък с публикации и когато се щракне върху публикация, публикацията се зарежда на нова страница с всички нейни коментари:
import { Post } from '/src/resources'; let postsPage = await Post .order({ createdAt: 'desc' }) .select('content') .perPage(10) .all();
Горната заявка ще извлече 10-те най-скорошни публикации и за оптимизиране единственият атрибут, който се зарежда, е техният content
Ако потребителят щракне върху бутон, за да премине към следващата страница на публикациите, манипулаторът на промени ще извлече следващата страница. Тук също деактивираме бутона, ако няма следващи страници.
onClickNextPage = async () => { postsPage = await postsPage.nextPage(); if (!postsPage.hasNextPage()) { /* disable next page button */ } };
Когато се кликне върху връзка към публикация, ние отваряме нова страница, като зареждаме и показваме публикацията с всички нейни данни, включително нейните коментари - известни като отговори, както и отговори на тези отговори:
import { Post } from '/src/resources'; onClick = async (postId) => { let post = await Post.includes({ replies: 'replies' }).find(postId); console.log(post.content, post.createdAt); post.replies().target().each(comment => { console.log( comment.content, comment.replies.target().map(reply => reply.content).toArray() ); }); }
Обажда се .target()
на hasMany
връзка като post.replies()
ще върне ActiveResource.Collection
на коментари, които са били заредени и съхранявани локално.
Това разграничение е важно, защото post.replies().target().first()
ще върне първия зареден коментар. За разлика от тях, post.replies().first()
ще върне обещание за един коментар, поискан от GET /api/v1/posts/:id/replies
.
основните външни потребители на бизнес планове са
Можете също така да поискате отговорите за публикация отделно от заявката за самата публикация, което ви позволява да модифицирате заявката си. Можете да веригирате модификатори като order
, select
, includes
, where
, perPage
, page
при заявка hasMany
взаимоотношения, точно както можете при заявка на самите ресурси.
import { Post } from '/src/resources'; onClick = async (postId) => { let post = await Post.find(postId); let userComments = await post.replies().where({ user: user }).perPage(3).all(); console.log('Your comments:', userComments.map(comment => comment.content).toArray()); }
Понякога искате да вземете данните от сървъра и да ги промените, преди да ги използвате. Например, можете да увиете post.createdAt
в moment()
обект, така че да можете да покажете удобна за потребителя дата и час за потребителя за това кога е създадена публикацията:
// /src/resources/Post.js import moment from 'moment'; class Post extends library.Base { static define() { /* ... */ this.afterRequest(function() { this.createdAt = moment(this.createdAt); }); } }
Ако работите със система за управление на състоянието, която благоприятства неизменяемите обекти, цялото поведение в ActiveResource.js може да бъде направено неизменяемо чрез конфигуриране на библиотеката с ресурси:
// /src/resources/library.js import { createResourceLibrary } from 'active-resource'; const library = createResourceLibrary( 'http://example.com/api/v1', { immutable: true } ); export default library;
За да приключите, ще ви покажа как да интегрирате вашата система за удостоверяване на потребителя във вашия User
ActiveResource
.
Преместете системата си за удостоверяване на маркера в крайната точка на API /api/v1/tokens
. Когато имейл и парола на потребител са изпратени до тази крайна точка, в отговор ще бъдат изпратени удостоверените данни на потребителя плюс токенът за упълномощаване.
Създайте Token
ресурсен клас, който принадлежи към User
:
// /src/resources/Token.js import library from './library'; class Token extends library.Base { static define() { this.belongsTo('user'); } } export default library.createResource(Token);
Добавете Token
до /src/resources/index.js
.
След това добавете статичен метод authenticate
към вашия User
ресурсен клас и свързват User
до Token
:
// /src/resources/User.js import library from './library'; import Token from './Token'; class User { static define() { /* ... */ this.hasOne('token'); } static async authenticate(email, password) { let user = this.includes('token').build({ email, password }); let authUser = await this.interface().post(Token.links().related, user); let token = authUser.token(); library.headers = { Authorization: 'Bearer ' + token.id }; return authUser; } }
Този метод използва resourceLibrary.interface()
, който в този случай е интерфейсът JSON: API, за изпращане на потребител до /api/v1/tokens
. Това е валидно: Крайна точка в JSON: API не изисква единствените типове, публикувани в и от нея, да са тези, на които е кръстено. Така че заявката ще бъде:
{ 'data': { 'type': 'users', 'attributes': { 'email': ' [email protected] ', 'password': 'password' } } }
Отговорът ще бъде удостовереният потребител с включен токен за удостоверяване:
{ 'data': { 'type': 'users', 'id': '1', 'attributes': { 'email': ' [email protected] ', 'user_name': 'user1', 'admin': false }, 'relationships': { 'token': { 'data': { 'type': 'tokens', 'id': 'Qcg6yI1a5qCxXgKWtSAbZ2MIHFChHAq0Vc1Lo4TX', } } } }, 'included': [{ 'type': 'tokens', 'id': 'Qcg6yI1a5qCxXgKWtSAbZ2MIHFChHAq0Vc1Lo4TX', 'attributes': { 'expires_in': 3600 } }] }
След това използваме token.id
да зададем библиотеката на Authorization
и върнете потребителя, което е същото като искането на потребителя чрез User.find()
както правехме преди.
Сега, ако се обадите User.authenticate(email, password)
, ще получите удостоверен потребител в отговор и всички бъдещи заявки ще бъдат упълномощени с токен за достъп.
В този урок изследвахме начините, по които ActiveResource.js може да ви помогне бързо да изградите JavaScript SDK за управление на вашите API ресурси и техните различни, понякога сложни, свързани ресурси. Можете да видите всички тези функции и по-документирани в README за ActiveResource.js .
Надявам се, че сте се насладили на лекотата, с която могат да се извършват тези операции, и че ще използвате (а може би дори ще допринесете) моята библиотека за вашите бъдещи проекти, ако отговаря на вашите нужди. В духа на отворения код, PR винаги са добре дошли!
SDK означава „комплект за разработка на софтуер“, което в контекст на JavaScript често означава библиотека за взаимодействие със специфичен REST API.
ORM означава „обектно-релационно картографиране“, техника за работа с обекти, без да се налага да мислим откъде идват или как се съхраняват. Например, ORM библиотека може да абстрахира API или необходимостта от знания за SQL, в зависимост от слоя, в който се използва.
CRUD услуга, напр. основен REST API, е този, който осигурява интерфейс за извършване на операции „създаване, четене, актуализиране и изтриване“. С други думи, той позволява основни операции за съхранение на данни, съответстващи на SQL командите INSERT, SELECT, UPDATE и DELETE и на HTTP глаголите POST, GET, PUT / PATCH и DELETE.
API осигурява абстракция на услуга, улесняваща работата с нея, обикновено с цел да бъде достъпна за по-широка група програмисти. Например API на Trello ви позволява програмно да създавате нови карти Trello чрез HTTP заявки.
Ключът на API е таен маркер, използван за формулиране на заявки, позволяващ на доставчика на услуги на API да идентифицира положително заявителя, ако приеме, че остава таен.
Управлението на съдържанието обикновено се отнася до създаване и организиране на текстово съдържание, което да бъде публикувано в интранет или в интернет. Най-популярната система за управление на съдържанието (CMS) през последните няколко години е WordPress.
Това е съвпадение на термините, но вероятно се отнася до RDBMS, например SQL Server, MySQL или PostgreSQL. ORM инструментите понякога помагат да се съпоставят обектите с SQL back-end. ActiveResource.js абстрахира различен слой, като съпоставя обекти с крайни точки на REST API.