Кратко
СкопированоРазные программы могут быть написаны на разных языках.
Это очевидно, и на первый взгляд кажется, что не вызывает никаких проблем. На деле же, если программы написаны на разных языках, их может быть трудно «подружить» и сделать так, чтобы они могли друг с другом «общаться».
Именно для того, чтобы подружить разные модули, системы, языки, программы — и существуют API.
Давайте сразу рассмотрим пример: мы работаем в «Twitter» и делаем фичу для браузерного приложения на JavaScript.
Когда нам нужны какие-то данные, мы запрашиваем их у сервера. Однако сервер написан, скорее всего, не на JavaScript, а на каком-то другом языке: Python, C#, Java. Чтобы сервер понял, что мы от него хотим, нам нужно как-то объяснить наш запрос.
Именно для этого нужно API — оно позволяет разным системам общаться, понимая друг друга.
API (Application Programming Interface) — это набор фич, которые одна программа представляет всем остальным. Она как бы говорит: «Смотрите, со мной можно говорить вот так и вот так, можете меня спросить о том-то через эту часть, а попросить что-то сделать — через эту».
В случае c клиент-серверным общением (см. Как работают веб-приложения) API может выступать как набор ссылок, по которым клиент обращается на сервер:
POST
— для создания пользователя;/ api / v1 . 0 / users GET
— для получения списка пользователей./ api / v1 . 0 / users
О том, какие бывают виды ссылок и принципы их построения, мы поговорим чуть ниже.
API может использоваться не только для общения браузера и сервера, но и в принципе для общения разных программ друг с другом.
Представьте, что вы написали модуль Credit
, который считает проценты по кредитам какого-нибудь банка. Чтобы воспользоваться этим модулем в других частях программы, вы экспортируете его функции наружу. Эти функции — это API этого модуля.
Или, например, вы написали плагин для Gulp, который минифицирует HTML-код. Если вы пользовались функциями, которые Gulp предоставляет, вы пользовались Gulp API.
Или вы пишете программку для Arduino, которая автоматически включает свет, если в комнате стало темно. Работая с Arduino SDK, вы пользуетесь их API.
Идеальное API
СкопированоВ идеале всем хочется, чтобы общение между системами было бесшовным, мгновенным, понятным и поставляло все те данные и действия, которые требуются. Но на деле добиться этого бывает очень трудно.
Бесшовность
СкопированоВсегда хочется, чтобы единожды написанную функциональность можно было использовать везде. Но у каждого проекта своя специфика, из-за чего написанная функция может не подойти.
Невозможно написать «универсальный модуль», который бы подошёл к любому проекту. Но зато возможно написать модуль, который бы был достаточно абстрактным. В таком случае, чтобы добавить его в проект, нам потребуется какое-то количество дополнительного кода, но мы сможем использовать уже написанные функции.
Дизайн API, при котором такое использование доставляет меньше всего проблем — наиболее бесшовный.
Быстродействие
СкопированоВернёмся к Twitter API. Если мы хотим создать нового пользователя, нам нужно передать на сервер данные об этом пользователе. Мы не можем сделать это с помощью JS-объекта, потому что сервер использует другой язык. Значит, нам надо «перевести» данные на какой-то промежуточный язык (чаще всего это JSON).
Та же история с получением данных с сервера. Серверу надо получить данные из базы данных, перевести их в какой-то язык, который понятен клиенту, и отправить.
Всё это требует времени. Чем меньше времени тратится на общение и выполнение нужных действий, тем лучше спроектировано API.
Понятность
СкопированоЧем точнее названы функции, методы или ссылки в API, тем меньше заблуждений и ошибок будет возникать при работе с ним.
Полнота
СкопированоКак правило, разработчикам хочется производить как можно меньше операций, из-за чего «перевод» может оказаться неточным: в нём может не хватать данных или, наоборот, быть слишком много.
Чем грамотнее спроектировано API (а скорее даже вся программная система), тем более полным будет ответ на каждое конкретное действие.
В идеальном API все эти проблемы решены, но идеального API не существует 😃
Какие API бывают
СкопированоВ этой статье мы сосредоточимся на клиент-серверной архитектуре, потому что в веб-разработке под API чаще всего имеют в виду именно запросы к серверу.
Прочитайте статью «Как работают веб-приложения», чтобы глубже разобраться в клиент-серверной архитектуре.
REST
СкопированоREST (Representational State Transfer) — стиль общения компонентов, при котором все необходимые данные указываются в параметрах запроса.
REST сейчас — один из самых распространённых стилей API в интернете.
Отличительная особенность этого стиля — это стиль построения адресов и выбор метода. Всё взаимодействие между клиентом и сервером сводится к 4 операциям (CRUD):
- созданию чего-либо, например, объекта пользователя (create, C);
- чтению (read, R);
- обновлению (update, U);
- удалению (delete, D).
Для каждой из операций есть собственный HTTP-метод:
POST
для создания;GET
для чтения;PUT
,PATCH
для обновления;DELETE
для удаления.
Разница между PUT
и PATCH
в том, что PUT
обновляет объект целиком, а PATCH
— только указанное поле.
Адрес, как правило, остаётся почти одинаковым, а детали запроса указываются в HTTP-методе и параметрах или теле запроса.
Например
Если бы мы писали API для интернет-магазина, то CRUD для заказа мог бы выглядеть следующим образом:
POST
— создать новый заказ. Как правило, в ответ на POST-запрос сервер возвращает ID созданной сущности, в нашем случае — ID заказа. Пусть будет 42./ api / orders / GET
— получить заказ с номером 42. В ответ мы получим JSON, XML, HTML с данными о заказе (сейчас чаще всего — JSON)./ api / orders / 42 PUT
— обновить заказ с номером 42. Вместе с запросом мы отправляем данные, которыми надо обновить этот заказ. В ответ сервер ответит или статусом 204 (всё хорошо, но контента в ответе нет), или ID обновлённой сущности./ api / orders / 42 DELETE
— удалить заказ с номером 42. Как правило, в ответ присылается или 204, или ID удалённой сущности./ api / orders / 42
Чаще всего при работе с API веб-сервисов вам будет попадаться именно REST или что-то похожее на него.
Плюсы:
- самый распространённый стиль;
- использует фундаментальную технологию (HTTP), как основу;
- достаточно легко читается.
Минусы:
- если спроектирован плохо, может отправлять или слишком много информации, либо слишком мало. (Но для обхода этой проблемы можно использовать backend for frontend).
SOAP
СкопированоВообще, не очень корректно сравнивать SOAP и REST, потому что REST — это архитектурный стиль, а SOAP — формат обмена данными. Поэтому мы не будем их сравнивать, а просто расскажем, как работает SOAP.
SOAP (Simple Object Access Protocol) — формат обмена данными.
Это структурированный формат обмена данными, то есть каждое сообщение следует определённой структуре. Чаще всего вместе с SOAP используется XML для отражения этой структуры.
Сама структура выглядит так:
- Envelope — корневой элемент, который определяет само сообщение.
- Header содержит атрибуты сообщения, например: информацию о безопасности.
- Body содержит сообщение, которым обмениваются приложения.
- Fault необязательный элемент с ошибками обработки, если они были.
Сообщение-запрос к интернет-магазину может выглядеть так:
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <getOrderDetails xmlns="https://example-store.com/orders"> <orderID>42</orderID> </getOrderDetails> </soap:Body></soap:Envelope>
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <getOrderDetails xmlns="https://example-store.com/orders"> <orderID>42</orderID> </getOrderDetails> </soap:Body> </soap:Envelope>
А ответ:
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <getOrderDetailsResponse xmlns="https://example-store.com/orders"> <getOrderDetailsResult> <orderID>42</orderID> <userID>43</userID> <dateTime>2020-10-10T12:00:00</dateTime> <products> <productID>1</productID> <productID>23</productID> <productID>45</productID> </products> </getOrderDetailsResult> </getOrderDetailsResponse> </soap:Body></soap:Envelope>
<?xml version="1.0" encoding="utf-8"?> <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <getOrderDetailsResponse xmlns="https://example-store.com/orders"> <getOrderDetailsResult> <orderID>42</orderID> <userID>43</userID> <dateTime>2020-10-10T12:00:00</dateTime> <products> <productID>1</productID> <productID>23</productID> <productID>45</productID> </products> </getOrderDetailsResult> </getOrderDetailsResponse> </soap:Body> </soap:Envelope>
При этом в SOAP неважно, каким методом передавать сообщения, в отличие от REST.
SOAP не очень прижился, потому что достаточно многословен и неудобен для работы на клиенте: XML проигрывает JSON, а SOAP, построенный на JSON — это довольно редкий случай.
Плюсы:
- не зависит от методов передачи;
- есть структура сообщения.
Минусы
- многословен;
- проигрывает REST в простоте.
RPC
СкопированоRPC (Remote Procedure Call) — это такой стиль, при котором в сообщении запроса хранится и действие, которое надо выполнить, и данные, которые для этого действия нужны.
Так как мы больше говорим о вебе, то можно грубо сказать, что RPC — это «вызов серверной функциональности из браузера».
В вебе более часто использовались XML-RPC и JSON-RPC. Мы будем рассматривать примеры на JSON-RPC, просто потому что JSON сейчас используется чаще, и его проще читать.
Сообщение-запрос по протоколу JSON-RPC должно иметь 3 обязательных поля:
method
— строка с именем вызываемого метода.params
— массив данных, которые должны быть переданы методу, как параметры.id
— значение любого типа, которое используется для установки соответствия между запросом и ответом.
В ответ сервер должен прислать сообщение, содержащее:
result
— данные, которые вернул метод. Если произошла ошибка во время выполнения метода, это свойство должно быть установлено вnull
.error
— код ошибки, если произошла ошибка во время выполнения метода, иначеnull
.id
— то же значение, что и в запросе, к которому относится этот ответ.
На примере всё с тем же магазином, получение заказа было бы реализовано примерно так:
Запрос:
{ // Для последней спецификации следует указывать версию: "jsonrpc": "2.0", // Далее указываем метод: "method": "orders.get", // В параметрах указываем ID заказа, // который нас интересует: "params": [42], // ID этого запроса. // Он может понадобиться, // когда система обрабатывает несколько запросов параллельно "id": 1}
{ // Для последней спецификации следует указывать версию: "jsonrpc": "2.0", // Далее указываем метод: "method": "orders.get", // В параметрах указываем ID заказа, // который нас интересует: "params": [42], // ID этого запроса. // Он может понадобиться, // когда система обрабатывает несколько запросов параллельно "id": 1 }
Успешный ответ:
{ "jsonrpc": "2.0", // В result данные о заказе: "result": { "orderId": 42, "userId": 43, "dateTime": "2020-10-10T12:00:00", "products": [ { "productID": 1 }, { "productID": 23 }, { "productID": 45 } ] }, "error": null, "id": 1}
{ "jsonrpc": "2.0", // В result данные о заказе: "result": { "orderId": 42, "userId": 43, "dateTime": "2020-10-10T12:00:00", "products": [ { "productID": 1 }, { "productID": 23 }, { "productID": 45 } ] }, "error": null, "id": 1 }
Ответ с ошибкой:
{ "jsonrpc": "2.0", "result": null, "error": "Order not found", "id": 1}
{ "jsonrpc": "2.0", "result": null, "error": "Order not found", "id": 1 }
Плюсы:
- есть структура сообщения;
- использует JSON, что делает его проще для чтения и написания;
- производителен, если нужны batch-запросы.
Минусы:
- слишком много логики уходит на клиент;
- HTTP-кэширование недоступно.
На практике
Скопированосоветует Скопировано
Иногда мы пользуемся какими-то API даже того не замечая, а иногда не можем разобраться, чего от нас хотят. Вот два разных примера:
Браузерное API
СкопированоОдни из самых знакомых фронтенд-разработчикам API — браузерные :–) Всё, что по умолчанию есть в window
— это браузерное API.
Мы описали самые важные из них в статье «Браузерное окружение, BOM»
Когда мы пользуемся консолью для отладки, мы тоже используем это API:
console.log("Is the error here?")
console.log("Is the error here?")
Или когда обращаемся к local
:
localStorage.setItem("key", "value")
localStorage.setItem("key", "value")
Twitter API
СкопированоУ Твитера, в принципе, не самое плохое API, которое можно встретить, хотя и дико запутанная документация.
Например, чтобы получить список твитов:
- Надо использовать метод GET.
- URL для запроса —
https
.: / / api . twitter . com / 2 / tweets - Обязательный аргумент —
ids
, чтобы указать, какие именно твиты надо получить. Технически вот этот адрес:https
запрошенный через GET должен сработать, но нужно использовать HTTP-заголовки для аутентификации: / / api . twitter . com / 2 / tweets ? ids = 1261326399320715264 , 1278347468690915330 Authorization : Bearer $BEARER _ TOKEN
И только при выполнении всех эти условий, можно получить ответ на запрос:
const response = await fetch( "https://api.twitter.com/2/tweets?ids=1261326399320715264,1278347468690915330", { // можно не указывать, так как он по умолчанию method: "GET", headers: { Authorization: `Bearer ${BEARER_TOKEN}`, }, })await response.json()
const response = await fetch( "https://api.twitter.com/2/tweets?ids=1261326399320715264,1278347468690915330", { // можно не указывать, так как он по умолчанию method: "GET", headers: { Authorization: `Bearer ${BEARER_TOKEN}`, }, } ) await response.json()
Ответ:
{ "data": [ { "id": "1261326399320715264", "text": "Tune in to the @MongoDB @Twitch stream featuring our very own @suhemparack to learn about Twitter Developer Labs - starting now! https://t.co/fAWpYi3o5O" }, { "id": "1278347468690915330", "text": "Good news and bad news: \n\n2020 is half over" } ]}
{ "data": [ { "id": "1261326399320715264", "text": "Tune in to the @MongoDB @Twitch stream featuring our very own @suhemparack to learn about Twitter Developer Labs - starting now! https://t.co/fAWpYi3o5O" }, { "id": "1278347468690915330", "text": "Good news and bad news: \n\n2020 is half over" } ] }