1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-08-10 21:51:42 +02:00

corrections

This commit is contained in:
Sergey Konstantinov
2023-05-09 09:45:23 +03:00
parent 34a7f598a5
commit 1c06a65307
6 changed files with 127 additions and 31 deletions

View File

@@ -6,7 +6,7 @@ Still, two main scenarios dominate the stage when we talk about API development:
* developing client-server applications
* developing client SDKs.
In the first case, we almost universally talk about APIs working atop the HTTP protocol. Today, the only notable examples of non-HTTP-based client-server interaction protocols are WebSocket (though it might, and frequently does, work in conjunction with HTTP) and highly specialized APIs like media streaming and broadcasting formats.
In the first case, we almost universally talk about APIs working atop the HTTP protocol. Today, the only notable examples of non-HTTP-based client-server interaction protocols are WebSocket (though it might, and frequently does, work in conjunction with HTTP), MQTT, and highly specialized APIs like media streaming and broadcasting formats.
#### HTTP API

View File

@@ -3,8 +3,8 @@
In the previous chapter, we concluded with the following interface that allows minimizing collisions while creating orders:
```
const pendingOrders = await api.
getOngoingOrders();
const pendingOrders = await api
.getOngoingOrders();
{ orders: [{
order_id: <task identifier>,
@@ -334,7 +334,7 @@ If none of the approaches above works, our only solution is changing the subject
// Retrieve all the events older
// than the one with the given id
GET /v1/orders/created-history⮠
older_than=<item_id>&limit=<limit>
?older_than=<item_id>&limit=<limit>
{
"orders_created_events": [{

View File

@@ -1 +1,69 @@
### [Bidirectional Data Flows. Push and Poll Models][api-patterns-push-vs-poll]
### [Bidirectional Data Flows. Push and Poll Models][api-patterns-push-vs-poll]
In the previous chapter, we discussed the following case: a partner receives information about new events that occurred in the system by periodically requesting an endpoint that supports retrieving ordered lists.
```
GET /v1/orders/created-history⮠
older_than=<item_id>&limit=<limit>
{
"orders_created_events": [{
"id",
"occured_at",
"order_id"
}, …]
}
```
This pattern (known as [*polling*](https://en.wikipedia.org/wiki/Polling_(computer_science))) is the most common approach to organizing two-way communication in an API when a partner needs not only to send some data to the server but also to receive notifications from the server about changes in some state.
Although this approach is quite easy to implement, polling always requires a compromise between responsiveness, performance, and system throughput:
* The longer the interval between consecutive requests, the greater the delay between the change of state on the server and receiving the information about it on the client, and the potentially larger the traffic volume that needs to be transmitted in one iteration.
* On the other hand, the shorter this interval, the more requests will be made in vain, as no changes in the system have occurred during the elapsed time.
In other words, polling always creates some background traffic in the system but never guarantees maximum responsiveness. Sometimes this problem is solved using so-called [“long polling”](https://en.wikipedia.org/wiki/Push_technology#Long_polling), i.e., by intentionally delaying the server's response for a long time (seconds, tens of seconds) until some state change occurs. However, we do not recommend using this approach in modern systems due to the associated technical problems (in particular, in unreliable network conditions, the client has no way of knowing that the connection is actually lost and a new request needs to be sent).
If it turns out that regular polling is not enough to solve user problem, you can switch to a reverse model (*push*): the server itself informs the client that changes have occurred in the system.
Although the problem and ways to solve it look similar, completely different technologies are currently used to deliver messages from the backend to the backend and from the backend to the client device.
#### Delivering Notifications to Client Devices
Since various mobile platforms currently constitute a major share of all client devices, this implies significant limitations in terms of battery (and partly traffic) saving on the technologies for data exchange between the server and the end user. Many platform and device manufacturers monitor the resources consumed by the application and can send it to the background or close open connections. In such a situation, frequent polling should only be used in active phases of the application work cycle (i.e., when the user is directly interacting with the UI) or in controlled environments (for example, if employees of a partner company use the application in their work and can add it to system exceptions).
Three alternatives to polling might be proposed:
##### Duplex Connections
The most obvious option is using technologies that can transmit messages in both directions over a single connection. The best-known example of such technology is [WebSocket](https://websockets.spec.whatwg.org/). Sometimes, [the Server Push functionality of the HTTP/2 protocol](https://datatracker.ietf.org/doc/html/rfc7540#section-8.2) is used for this purpose; however, we must note that the specification formally does not allow such usage. There is also the [WebRTC](https://www.w3.org/TR/webrtc/) protocol; its main purpose is a peer-to-peer exchange of media data, and it's rarely used in client-server interaction.
Although the idea looks simple and attractive, its applicability to real-world use cases is limited. Popular server software and frameworks do not support server-initiated message sending (gRPC does support it, but the client should initiate the exchange; using gRPC server streams to send server-initiated events is essentially employing HTTP/2 server pushes for this purpose, and it's the same technique as in long polling approach, just a bit more modern), and the existing specification definition standards do not support it — as WebSocket is a low-level protocol, and you will need to design the interaction format on your own.
Duplex connections are still suffering from the unreliability of the network and require implementing additional tricks to tell the difference between a network problem and the absence of new messages. All these issues result in limited applicability of the technology; it's mostly used in web applications.
##### Separate Callback Channel
Instead of a duplex connection, two separate connections might be used: one for sending requests to server and one to receive notifications from server. The most popular technology of this kind is [MQTT](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html). Although it's considered very effective because of using low-level protocols, its disadvantages follow from its advantages:
* The technology is meant to implement the pub/sub pattern, and its main value is that the server software (MQTT Broker) is provider alongside the protocol itself. Applying to to different tasks, especially bidirectional communication, might be challenging.
* The low-level protocols force you to develop your own data formats.
There is also a Web standard for sending server notifications called [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html) (SSE). However, it's less functional than WebSocket (only text data and unidirectional flow are allowed) and rarely used.
##### Third-Party Push Notifications
One of the notorious problems with the long polling / WebSocket / SSE / MQTT technologies is the necessity to maintain an open network connection between the client and the server, which might be a problem for mobile applications and IoT devices from the performance and battery-saving point of view. One of the options that allow to mitigate the issue is delegating sending push notifications to a third-party service (the most popular choice today is Google's Firebase Cloud Messaging) that deliver notifications through the built-in mechanisms of the platform. Using such integrated services takes off the most of the load from the developer's shoulders related to maintaining open connections and checking their status. The disadvantages of using third-party services is the necessity to pay for them and strict limits on message sizes.
Also, sending push notifications to end-user devices suffers from one important issue: the percentage of successfully delivered message never reaches 100%; message drop rate might be tens of per cent. Taking into account the message size limitations, it's actually better to implement mixed model than pure push model: the client continues polling the server, just less frequently, and push notifications just trigger ahead-of-time polling. (This problem is actually applicable to any notification delivery technology. Low-level protocols offer more options to set delivery guarantees; however, given the situation with forceful closing open connections by OSes, having in an application low-frequency polling as a precaution is almost never a bad thing.)
#### Using Push Technologies in Public APIs
As a consequence of the fragmentation of client technologies described above, it's virtually impossible to use any of them but polling in public APIs. Requiring partners implement receiving notifications through WebSocket, MQTT, or SSE channels raises the bar of adopting the API as working with low-level protocols, poorly covered by existing IDLs and code-generation tools into the bargain, requires significant amount of effort and is prone to implementation errors. If you decide to provide ready-to-use SKDs to ease working with the API, you will need to develop it for every applicable platform (which is, let us reiterate, quite labor-consuming). Given that HTTP-polling is *much* easier to implement and its disadvantages play their role only in the situations when one *really* needs to think about saving traffic and computational resources, we would rather recommend exposing additional channels of receiving server-sent notifications *as an addition* to polling, not instead of it.
Using platform pushes might be a fine solution for public APIs, but there another problem arises: application developers are not eager to allow other third-party services send push notifications, and that's for a list of reasons, starting with the costs of sending pushes and ending with security considerations.
In fact, the most convenient way of organizing message delivery from the public API backend to a partner service's user is delivering message backend-to-backend, so the partner service might relay it further using push notification or any other technology that partner selected for developing their applications.
#### Delivering Backend-to-Backend Notifications
Unlike client applications, server-side messaging implementations stick to a single approach to implement bidirectional data flow

View File

@@ -6,7 +6,7 @@
* разработка клиент-серверных приложений;
* разработка клиентских SDK.
В первом случае мы говорим практически исключительно об API, работающих поверх протокола HTTP. В настоящий момент клиент-серверное взаимодействие, не опирающееся на HTTP, можно встретить разве что в протоколе WebSocket (хотя и он может быть использован совместно с HTTP, что часто и происходит), а также в различных форматах потокового вещания и других узкоспециализированных интерфейсах.
В первом случае мы говорим практически исключительно об API, работающих поверх протокола HTTP. В настоящий момент клиент-серверное взаимодействие, не опирающееся на HTTP, можно встретить разве что в протоколах WebSocket (хотя и он может быть использован совместно с HTTP, что часто и происходит) и MQTT, а также в различных форматах потокового вещания и других узкоспециализированных интерфейсах.
#### HTTP API

View File

@@ -3,8 +3,8 @@
В предыдущей главе мы пришли вот к такому интерфейсу, позволяющему минимизировать коллизии при создании заказов:
```
const pendingOrders = await api.
getOngoingOrders();
const pendingOrders = await api
.getOngoingOrders();
{ orders: [{
order_id: <идентификатор задания>,
@@ -336,7 +336,7 @@ POST /v1/orders/archive/retrieve
// заказа, более старые,
// чем запись с указанным id
GET /v1/orders/created-history⮠
older_than=<item_id>&limit=<limit>
?older_than=<item_id>&limit=<limit>
{
"orders_created_events": [{

View File

@@ -1,6 +1,6 @@
### [Двунаправленные потоки данных. Push и poll-модели][api-patterns-push-vs-poll]
В предыдущей главе мы рассмотрели следующий кейс: партнёр получает информацию о новых событиях, произошедших в системе, периодически опрашивая эндпойнт, поддерживающий отдачу упорядоченных списков:
В предыдущей главе мы рассмотрели следующий кейс: партнёр получает информацию о новых событиях, произошедших в системе, периодически опрашивая эндпойнт, поддерживающий отдачу упорядоченных списков.
```
GET /v1/orders/created-history⮠
@@ -21,7 +21,7 @@ GET /v1/orders/created-history⮠
* чем длиннее интервал между последовательными запросами, тем больше будет задержка между изменением состояния на сервере и получением информации об этом на клиенте, и тем потенциально большим будет объём данных, которые необходимо будет передать за одну итерацию;
* с другой стороны, чем этот интервал короче, чем большее количество запросов будет совершаться зря, т.к. никаких изменений в системе за прошедшее время не произошло.
Иными словами, поллинг всегда создаёт какой-то фоновый трафик в системе, но никогда не гарантирует максимальной отзывчивости. Иногда эту проблему решают с помощью «долгого поллинга» ([long polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling)) — т.е. целенаправленно замедляют отдачу сервером ответа на длительное (секунды, десятки секунд) время — однако мы не рекомендуем использовать этот подход в современных системах из-за связанных технических проблем (в частности, в условиях ненадёжной сети у клиента нет способа понять, что соединение на самом деле потеряно, и нужно отправить новый запрос, а не ожидать ответа на текущий).
Иными словами, поллинг всегда создаёт какой-то фоновый трафик в системе, но никогда не гарантирует максимальной отзывчивости. Иногда эту проблему решают с помощью «долгого поллинга» ([long polling](https://en.wikipedia.org/wiki/Push_technology#Long_polling)) — т.е. целенаправленно замедляют отдачу сервером ответа на длительное (секунды, десятки секунд) время до тех пор, пока на сервере не появится сообщение для передачи — однако мы не рекомендуем использовать этот подход в современных системах из-за связанных технических проблем (в частности, в условиях ненадёжной сети у клиента нет способа понять, что соединение на самом деле потеряно, и нужно отправить новый запрос, а не ожидать ответа на текущий).
Если оказывается, что обычного поллинга для решения пользовательских задач недостаточно, то можно перейти к обратной модели (*push*): сервер *сам* сообщает клиенту, что в системе произошли изменения.
@@ -29,32 +29,49 @@ GET /v1/orders/created-history⮠
#### Доставка сообщений на клиентское устройство
Поскольку разнообразные мобильные платформы сейчас составляют значительную долю всех клиентских устройств, на технологии взаимного обмена данных между сервером и конечным пользователем накладываются значительные ограничения с точки зрения экономии заряда батареи (и отчасти трафика). Многие производители платформ и устройств следят за потребляемыми приложением ресурсами, и могут отправлять приложение в фон или вовсе закрывать открытые соединения. В такой ситуации частый поллинг стоит применять только в активных фазах работы приложения (т.е. когда пользователь непосредственно взаимодействует с UI) либо если приложение работает в контролируемой среде (например, используется сотрудниками компании-партнера непосредственно в работе, и может быть добавлено в системные исключения).
Поскольку разнообразные мобильные платформы и «умные устройства» (Internet of Things, IoT) сейчас составляют значительную долю всех клиентских устройств, на технологии взаимного обмена данных между сервером и конечным пользователем накладываются значительные ограничения с точки зрения экономии заряда батареи (и отчасти трафика). Многие производители платформ и устройств следят за потребляемыми приложением ресурсами, и могут отправлять приложение в фон или вовсе закрывать открытые соединения. В такой ситуации частый поллинг стоит применять только в активных фазах работы приложения (т.е. когда пользователь непосредственно взаимодействует с UI) либо если приложение работает в контролируемой среде (например, используется сотрудниками компании-партнера непосредственно в работе, и может быть добавлено в системные исключения).
Альтернатив поллингу на данный момент можно предложить две.
Альтернатив поллингу на данный момент можно предложить три:
##### Дуплексные соединения
Самый очевидный вариант — использование технологий, позволяющих передавать по одному соединению сообщения в обе стороны. Наиболее известной из таких технологий является [WebSockets](https://websockets.spec.whatwg.org/). Иногда для организации полнодуплексного соединения применяется [Server Push, предусмотренный протоколом HTTP/2](https://datatracker.ietf.org/doc/html/rfc7540#section-8.2), однако надо отметить, что формально спецификация не предусматривает такого использования.
Самый очевидный вариант — использование технологий, позволяющих передавать по одному соединению сообщения в обе стороны. Наиболее известной из таких технологий является [WebSockets](https://websockets.spec.whatwg.org/). Иногда для организации полнодуплексного соединения применяется [Server Push, предусмотренный протоколом HTTP/2](https://datatracker.ietf.org/doc/html/rfc7540#section-8.2), однако надо отметить, что формально спецификация не предусматривает такого использования. Также существует протокол [WebRTC](https://www.w3.org/TR/webrtc/), но он, в основном, используется для обмена медиа-данными между клиентами, редко для клиент-серверного взаимодействия.
Несмотря на то, что идея в целом выглядит достаточно простой и привлекательной, в реальности её использование довольно ограничено. Поддержки инициирования *сервером* отправки сообщения обратно на клиент практически нет в популярных протоколах и фреймворках (gRPC поддерживает потоки сообщений с сервера, но их всё равно должен инициировать клиент; использование потоков для пересылки сообщений по мере их возникновения — фактически то же самое использование HTTP/2 Server Push в обход спецификации), и существующие стандарты спецификаций API также не поддерживают такой обмен данными. WebSockets является низкоуровневым протоколом, и формат взаимодействия придётся разработать самостоятельно.
Несмотря на то, что идея в целом выглядит достаточно простой и привлекательной, в реальности её использование довольно ограничено. Поддержки инициирования *сервером* отправки сообщения обратно на клиент практически нет в популярном серверном ПО и фреймворках (gRPC поддерживает потоки сообщений с сервера, но их всё равно должен инициировать клиент; использование потоков для пересылки сообщений по мере их возникновения — то же самое использование HTTP/2 Server Push в обход спецификации, что, фактически, работает как тот же самый long polling, только чуть более современный), и существующие стандарты спецификаций API также не поддерживают такой обмен данными: WebSockets является низкоуровневым протоколом, и формат взаимодействия придётся разработать самостоятельно.
К тому же дуплексные соединения по-прежнему страдают от ненадёжной сети и требуют дополнительных ухищрений для того, чтобы отличить сетевую проблему от отсутствия новых сообщений. Всё это приводит к тому, что данная технология используется в основном веб-приложениями.
Дуплексные соединения по-прежнему страдают от ненадёжной сети и требуют дополнительных ухищрений для того, чтобы отличить сетевую проблему от отсутствия новых сообщений. Всё это приводит к тому, что данная технология используется в основном веб-приложениями.
##### Раздельный канал обратного вызова
Вместо дуплексных соединений можно использовать два раздельных канала — один для отправки сообщений на сервер, другой для получения сообщений с сервера. Наиболее популярной технологией такого рода является [MQTT](https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html). Хотя эта технология считается максимально эффективной в силу использования низкоуровневых протоколов, её достоинства порождают и её недостатки:
* технология в первую очередь предназначена для имплементации паттерна pub/sub и ценна наличием соответствующего серверного ПО (MQTT Broker); применить её для других задач, особенно для двунаправленного обмена данными, может быть сложно;
* низкоуровневый протокол диктует необходимость разработки собственного формата данных.
Существует также веб-стандарт отправки серверных сообщений [Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html) (SSE). Однако по сравнению с WebSocket он менее функциональный (только текстовые данные, однонаправленный поток сообщений) и поэтому используется редко.
##### Сторонние сервисы отправки push-уведомлений
Одна из неприятных особенностей технологии типа long polling / WebSockets — необходимость поддерживать открытое соединение между клиентом и сервером, что для мобильных приложений может быть проблемой с точки зрения производительности и энергопотребления. Один из вариантов решения этой проблемы — делегирование отправки уведомлений стороннему сервису (самым популярным выбором на сегодня является Firebase Cloud Messaging от Google), который в свою очередь доставит уведомление через встроенные механизмы платформы. Использование встроенных в платформу сервисов получения уведомлений снимает с разработчика головную боль по написанию кода, поддерживающего открытое соединение, и снижает риски неполучения сообщения. Недостатками third-party серверов сообщений является необходимость платить за них и ограничения на размер сообщения.
Одна из неприятных особенностей технологии типа long polling / WebSocket / SSE / MQTT — необходимость поддерживать открытое соединение между клиентом и сервером, что для мобильных приложений может быть проблемой с точки зрения производительности и энергопотребления. Один из вариантов решения этой проблемы — делегирование отправки уведомлений стороннему сервису (самым популярным выбором на сегодня является Firebase Cloud Messaging от Google), который в свою очередь доставит уведомление через встроенные механизмы платформы. Использование встроенных в платформу сервисов получения уведомлений снимает с разработчика головную боль по написанию кода, поддерживающего открытое соединение, и снижает риски неполучения сообщения. Недостатками third-party серверов сообщений является необходимость платить за них и ограничения на размер сообщения.
Независимо от выбранной технологии, отправка сообщений с сервера на устройство конечного пользователя страдает от одной большой проблемы: в результате всех описанных ограничений, процент успешной доставки уведомлений никогда не равен 100; потери сообщений могут достигать десятков процентов. С учётом ограничений на размер контента, **скорее правильно говорить не о push-модели, а о комбинированной**: приложение продолжает периодически опрашивать сервер, а пуши являются триггером для внеочередного опроса.
Кроме того, отправка push-уведомлений на устройство конечного пользователя страдает от одной большой проблемы: процент успешной доставки уведомлений никогда не равен 100; потери сообщений могут достигать десятков процентов. С учётом ограничений на размер контента, скорее правильно говорить не о push-модели, а о комбинированной: приложение продолжает периодически опрашивать сервер, а пуши являются триггером для внеочередного опроса. (На самом деле, это соображение в той или иной мере применимо к любой технологии доставки сообщений на клиент. Низкоуровневые протоколы предоставляют больше возможностей управлять гарантиями доставки, но, с учётом ситуации с принудительным закрытием соединений системой, иметь в качестве страховки низкочастотный поллинг в приложении почти никогда не бывает лишним.)
#### Использование push-технологий в публичном API
Следствием описанной выше фрагментации клиентских технологий является фактическая невозможность использовать любую из них кроме обычного поллинга в публичном API. Требование к партнёрам реализовать получение сообщений через WebSocket / MQTT / SSE каналы значительно повышает порог входа в API, т.к. работа с низкоуровневыми протоколами, к тому же плохо покрытыми существующими IDL и кодогенерацией, требует значительных ресурсов и чревата ошибками имплементации. Если же вы решите предоставлять готовый SDK к такому API, то вам придётся самостоятельно разработать его под каждую целевую платформу (что, повторимся, само по себе трудоёмко). Учитывая, что HTTP-поллинг кратно проще в реализации, а его недостатки проявляются только там, где *действительно* нужно экономить трафик и вычислительные ресурсы, мы склонны рекомендовать предоставлять альтернативные каналы получения сообщений только *в дополнение* к поллингу, но никак не вместо него.
Хорошим решением для публичного API могли бы стать системные пуши, но здесь возникает другая проблема: разработчики приложений не склонны давать сторонним сервисам право на отсылку push-уведомлений, и на то есть большой список причин, начиная от расходов на отправку и заканчивая проблемами безопасности.
Фактически самый удобный способ организовать доставку сообщений от бэкенда публичного API пользователю партнёрского сервиса — это доставить сообщение с бэкенда на бэкенд, чтобы сервис партнёра сам транслировал сообщение на клиенты через push-уведомления или любую другую технологию, которую партнёр выбрал для разработки своего приложения.
#### Доставка сообщений backend-to-backend
В отличие от клиентских приложений, серверные API практически безрезультативно используют единственный подход — отдельный канал связи для обратных вызовов. При интеграции партнёр указывает URL своего собственного сервера обработки сообщений, и сервер API вызывает этот эндпойнт (также называемый “webhook”) для оповещения о произошедшем событии. Хотя long polling, Web Sockets и HTTP/2 Push тоже вполне применимы для backend-2-backend взаимодействия, мы сходу затрудняемся назвать примеры популярных API, которые использовали бы эти технологии. Главными причинами такого положения дел нам видятся:
* бо́льшая критичность корректной и своевременной обработки событий, и отсюда повышенные требования к гарантиям их доставки;
* широкий выбор готовых компонентов для организации сервера webhook-ов (поскольку, фактически, это просто обычный веб-сервер);
* возможность описать такое взаимодействие спецификацией и использовать кодогенерацию.
В отличие от клиентских приложений, серверные API практически безальтернативно используют единственный подход для организации двустороннего взаимодействия [помимо поллинга, который работает на сервере точно так же, как и на клиенте, и имеет те же достоинства и недостатки] — отдельный канал связи для обратных вызовов. Этот канал может быть организован в виде очереди сообщений (в частности, через pub/sub топик) либо использование URL обратного вызова (т.н. «webhook»).
Предположим, что в нашем кофейном примере партнёр располагает некоторым бэкендом, готовым принимать оповещения о новых заказах, поступивших в его кофейни. Решение этой задачи декомпозируется на несколько шагов:
#### Webhook-и
При интеграции через webhook, партнёр указывает URL своего собственного сервера обработки сообщений, и сервер API вызывает этот эндпойнт для оповещения о произошедшем событии.
Предположим, что в нашем кофейном примере партнёр располагает некоторым бэкендом, готовым принимать оповещения о новых заказах, поступивших в его кофейни, и нам нужно договориться о формате взаимодействия. Решение этой задачи декомпозируется на несколько шагов:
##### Договоренность о контракте
@@ -63,7 +80,7 @@ GET /v1/orders/created-history⮠
* наоборот, партнёр должен разработать эндпойнт в стандартном формате, предлагаемом производителем API;
* любой промежуточный вариант.
Важно, что в любом случае должен существовать формальный контракт (очень желательно — в виде спецификации) на форматы запросов к эндпойнту-webhook-у, ответов и возникающие ошибки.
Важно, что в любом случае должен существовать формальный контракт (очень желательно — в виде спецификации) на форматы запросов и ответов эндпойнта-webhook-а и возникающие ошибки.
##### Договорённость о методах авторизации
@@ -71,21 +88,21 @@ GET /v1/orders/created-history⮠
##### API для задания адреса webhook-а
Так как callback-эндпойнт контролируется партнёром, жёстко зафиксировать его обычно не представляется возможным — должен существовать API (возможно, в виде кабинета партнёра) для задания URL webhook-а (и публичных ключей авторизации).
Так как callback-эндпойнт контролируется партнёром, жёстко зафиксировать его обычно не представляется возможным — должен существовать интерфейс (возможно, в виде кабинета партнёра) для задания URL webhook-а (и публичных ключей авторизации).
**Важно**. К операции задания адреса callback-а нужно подходить с максимально возможной серьёзностью (очень желательно требовать второй фактор авторизации для подтверждения этой операции), поскольку, получив доступ к такой функциональности, злоумышленник может совершить множество весьма неприятных атак:
* если указать в качестве приёмника сторонний URL, можно получить доступ к потоку всех заказов партнёра и при этом вызвать перебои в его работе;
* если указать в качестве webhook-а URL интранет-сервисов компании-провайдера API, можно осуществить [SSRF-атаку](https://en.wikipedia.org/wiki/SSRF) на инфраструктуру самой компании.
#### Типичные проблемы интеграций с обратными вызовами
#### Типичные проблемы интеграции через webhook
Двунаправленные интеграции (и клиентские, и серверные — хотя последние в большей степени) несут в себе очень неприятные риски для разработчика API. Если в общем случае качество работы API зависит в первую очередь от самого разработчика API, то в случае обратных вызовов всё в точности наоборот: качество работы интеграции напрямую зависит от того, как код webhook-эндпойнта написан партнёром. Мы можем столкнуться здесь с самыми различными видами проблем в партнёрском коде:
* false-positive ответы, когда сообщение не было обработано, но сервер партнёра тем не менее ошибочно вернул код успеха;
Двунаправленные интеграции (и клиентские, и серверные — хотя последние в большей степени) несут в себе очень неприятные риски для провайдера API. Если в общем случае качество работы API зависит в первую очередь от самого разработчика API, то в случае обратных вызовов всё в точности наоборот: качество работы интеграции напрямую зависит от того, как код webhook-эндпойнта написан партнёром. Мы можем столкнуться здесь с самыми различными видами проблем в партнёрском коде:
* webhook может возвращаеть false-positive ответы, когда сообщение не было обработано, но сервер партнёра тем не менее ошибочно вернул код успеха;
* и наоборот, возможны false-negative ответы, когда сообщение было обработано, но эндпойнт почему-то вернул ошибку;
* длительное время обработки запросов — возможно, настолько длительное, что сервер сообщений просто не будет успевать их отправить;
* ошибки в реализации идемпотентости, т.е. повторная обработка одного и того же сообщения партнёром может приводить к ошибкам или некорректности данных в системе партнёра;
* webhook может обрабатывать входящие запросы очень долго — возможно, настолько долго, что сервер сообщений просто не будет успевать их отправить;
* могут быть допущены ошибки в реализации идемпотентости, и повторная обработка одного и того же сообщения партнёром может приводить к ошибкам или некорректности данных в системе партнёра;
* размер тела сообщение может превысить лимит, выставленный на веб-сервере партнёра;
* наконец, эндпойнт может быть просто недоступен по множеству разных причин, он проблем в дата-центре, где расположены сервера, до банальной человеческой ошибки при смене URL webhook-а.
* наконец, эндпойнт может быть просто недоступен по множеству различных причин, от проблем в дата-центре, где расположены сервера партнёра, до банальной человеческой ошибки при смене URL webhook-а.
Очевидно, вы никак не можете гарантировать, что партнёр не совершил какую-то из перечисленных ошибок. Но вы можете попытаться минимизировать возможный ущерб:
@@ -99,3 +116,14 @@ GET /v1/orders/created-history⮠
3. Должна быть реализована система мониторинга состояния партнёрских эндпойнтов:
* при появлении большого числа ошибок (таймаутов) должно срабатывать оповещение (в т.ч. оповещение партнёра о проблеме), возможно, с несколькими уровнями эскалации;
* если в очереди скапливается большое количество необработанных событий, должен существовать механизм деградации (ограничения количества запросов в адрес партнёра — возможно в виде срезания спроса, т.е. частичного отказа в обслуживании конечных пользователей) и полного аварийного отключения партнёра.
#### Очереди сообщений
Хотя long polling, WebSocket, MQTT и HTTP/2 Push тоже вполне применимы для backend-2-backend взаимодействия, мы сходу затрудняемся назвать примеры популярных API, которые использовали бы эти технологии. Главными причинами такого положения дел нам видятся:
* меньшая критичность к проблемам производительности (у сервера фактически нет ограничений по расходу трафика, и поддержание открытых соединений тоже не является проблемой);
* широкий выбор готовых компонентов для разработки webhook-ов (поскольку, фактически, это просто обычный веб-сервер);
* возможность описать такое взаимодействие спецификацией и использовать кодогенерацию.
Однако при разработке взаимодействия бэкенд-бэкенд гораздо большее значение имеют гарантии доставки и отсутствие ошибок в обвязке, поскольку в случае проявления каких-то проблем во взаимодействии,
Для внутренних API оба способа являются равноценными, а вот в партнёрских и внешних API webhook-и — фактически единственный способ организации взаимодействия.