skip to main content

API. Что это и зачем нужно?

js

Кратко 🔗

Разные программы могут быть написаны на разных языках.

С первого взгляда кажется, что это очевидно и не доставляет никаких проблем. На деле же, если программы написаны на разных языках, их может быть трудно «подружить» и сделать так, чтобы они могли друг с другом общаться.

Именно для того, чтобы «подружить» разные модули, системы, языки, программы — и существуют API.

Давайте сразу рассмотрим пример: мы работаем в Twitter и делаем фичу для браузерного приложения. Мы работаем с JS.

Когда нам нужны какие-то данные, мы запрашиваем их у сервера. Однако сервер написан, скорее всего, не на JS, а на каком-то другом языке: Python, C#, Java. Чтобы сервер понял, что мы от него хотим, нам нужно как-то объяснить наш запрос.

Именно для этого нужно API — оно позволяет разным системам общаться, понимая друг друга.

API (Application Programming Interface) — это набор фич, которые одна программа представляет всем остальным. Она как бы говорит: «Смотрите, со мной можно говорить вот так и вот так, можете меня спросить о том-то через эту часть, а попросить что-то сделать — через эту».

В случае в клиент-серверным Как работают веб-приложения общением API может выступать набор ссылок, по которым клиент обращается на сервер:

/*
POST /api/v1.0/users — для создания пользователя;
GET /api/v1.0/users — для получения списка пользователей.
О том, какие бывают виды ссылок и принципы их построения,
мы поговорим чуть ниже.
*/

API может использоваться не только для общения браузера и сервера, но и в принципе для общения разных программ друг с другом.

Представьте, что вы написали модуль CreditCalculator, который считает проценты по кредитам какого-нибудь банка. Чтобы воспользоваться этим модулем в других частях программы, вы экспортируете его функции наружу. Эти функции — это API этого модуля.

Картинка банкомата внутри которого сидит человек. API — кнопки банкомата

Пользователь не видит, что происходит внутри, для него доступно только 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 /api/orders/ Создать новый заказ.
Как правило, в ответ на POST-запрос
сервер отвечает ID созданной сущности,
в нашем случае — ID заказа. Пусть будет 42.

- GET /api/orders/42 Получить заказ с номером 42.
В ответ мы получим JSON / XML / HTML
с данными о заказе (сейчас чаще всего — JSON).

- PUT /api/orders/42 Обновить заказ с номером 42.
Вместе с запросом мы отправляем данные,
которыми надо обновить этот заказ.
В ответ сервер ответит или статусом 204
(всё хорошо, но контента в ответе нет),
или ID обновлённой сущности.

- DELETE /api/orders/42 Удалить заказ с номером 42.
Как правило, в ответ присылается или 204,
или ID удалённой сущности.
*/

Чаще всего при работе с API веб-сервисов вам будет попадаться именно REST или что-то похожее на него.

Плюсы

  • Самый распространённый стиль;
  • Использует фундаментальную технологию (HTTP), как основу;
  • Достаточно легко читается.

Минусы

  • Если спроектирован плохо, может отправлять или слишком много информации, либо слишком мало. (Но для обхода этой проблемы можно использовать backend for frontend.)

SOAP 🔗

Вообще, не очень корректно «сравнивать SOAP и REST», потому что REST — это архитектурный стиль, а SOAP — формат обмена данными. Поэтому мы не то чтобы будем их сравнивать, а просто расскажем, как работает SOAP.

SOAP (Simple Object Access Protocol) — формат обмена данными.

Это структурированный формат обмена данными, то есть каждое сообщение следует определённой структуре. Чаще всего вместе с SOAP используется XML для отражения этой структуры.

Сама структура выглядит так:

Схема структуры SOAP пакета

  • 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>
<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",

// В 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
}

Плюсы

  • Есть структура сообщения;
  • Использует JSON, что делает его проще для чтения и написания;
  • Производителен, если нужны batch-запросы.

Минусы

  • Слишком много логики уходит на клиент;
  • HTTP-кеширование недоступно.

В работе 🔗

Иногда мы пользуемся какими-то API даже того не замечая, а иногда не можем разобраться, чего от нас хотят. Вот два разных примера:

Браузерное API 🔗

Одни из самых знакомых фронтенд-разработчикам API — браузерные :–) Всё, что по умолчанию есть в window — это браузерное API.

Мы описали самые важные из них в статье «Браузерное окружение, BOM»

скриншот доступных браузерных API

Когда мы пользуемся консолью для отладки, мы тоже используем это API:

console.log("Is the error here?")

Или когда обращаемся к localStorage:

localStorage.setItem("key", "value")

Twitter API 🔗

У Твитера, в принципе, не самое плохое API, которое можно встретить, хотя и дико запутанная документация.

Например, чтобы получить список твитов:

// 1. Чтобы получить список твитов, надо использовать метод GET.
// 2. URL для запроса — https://api.twitter.com/2/tweets.
// 3. Обязательный аргумент — ids,
// чтобы указать, какие именно твиты надо получить.
//
// Технически вот этот адрес:
// https://api.twitter.com/2/tweets?ids=1261326399320715264,1278347468690915330
// запрошенный через GET должен сработать,
// но нужно использовать HHTP-заголовки для аутентификации
// 4. 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()

/*
{
"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"
}
]
}
*/