Повторяй за мной:
REST и асинхронные события не являются альтернативами. Они полностью ортогональны.
Вы можете иметь один или другой, или оба, или ни того, ни другого. Это совершенно разные инструменты для совершенно разных проблемных областей. На самом деле общение между запросами и ответами общего назначения абсолютно способно быть асинхронным, управляемым событиями и отказоустойчивым .
В качестве тривиального примера протокол AMQP отправляет сообщения по TCP-соединению. В TCP каждый пакет должен быть подтвержден получателем . Если отправитель пакета не получает ACK для этого пакета, он продолжает повторную отправку этого пакета до тех пор, пока не получит подтверждение ACK или пока уровень приложения не «сдастся» и не прекратит соединение. Это явно не отказоустойчивая модель запрос-ответ, потому что каждый «запрос на отправку пакета» должен иметь сопровождающий «ответ на подтверждение пакета», а отказ ответить приводит к сбою всего соединения. Тем не менее, AMQP, стандартизированный и широко распространенный протокол асинхронной отказоустойчивой передачи сообщений, передается по протоколу TCP! Что дает?
Основная концепция, которая используется здесь, заключается в том, что масштабируемые слабосвязанные отказоустойчивые сообщения определяются тем, какие сообщения вы отправляете , а не тем, как вы их отправляете . Другими словами, слабая связь определяется на прикладном уровне .
Давайте рассмотрим две стороны, взаимодействующие либо напрямую с RESTful HTTP, либо косвенно с брокером сообщений AMQP. Предположим, что Сторона A желает загрузить изображение JPEG на Сторону B, которая будет повышать резкость, сжимать или иным образом улучшать изображение. Участник А не нуждается в обработанном изображении немедленно, но требует ссылки на него для будущего использования и поиска. Вот один путь, который может пойти в REST:
- Сторона A отправляет сообщение HTTP-
POST
запроса стороне B сContent-Type: image/jpeg
- Сторона B обрабатывает изображение (долгое время, если оно большое), пока сторона A ждет, возможно, делая другие вещи
- Сторона B отправляет
201 Created
ответное сообщение HTTP Стороне A с Content-Location: <url>
заголовком, который ссылается на обработанное изображение.
- Сторона А считает свою работу выполненной, поскольку теперь она имеет ссылку на обработанное изображение.
- Когда-нибудь в будущем, когда Сторона А нуждается в обработанном изображении, она ПОЛУЧАЕТ его, используя ссылку из предыдущего
Content-Location
заголовка
Код 201 Created
ответа сообщает клиенту, что он не только успешно выполнил запрос, но и создал новый ресурс. В ответе 201 Content-Location
заголовок является ссылкой на созданный ресурс. Это указано в RFC 7231, разделы 6.3.2 и 3.1.4.2.
Теперь давайте посмотрим, как это взаимодействие работает над гипотетическим протоколом RPC поверх AMQP:
- Сторона A отправляет брокеру сообщений AMQP (назовите его Messenger) сообщение, содержащее изображение и инструкции, чтобы направить его Стороне B для обработки, а затем отвечает Стороне A с каким-либо адресом для изображения
- Вечеринка А ждет, возможно, делает другие вещи
- Посланник отправляет исходное сообщение Стороны А Стороне Б
- Сторона B обрабатывает сообщение
- Сторона B отправляет Messenger сообщение, содержащее адрес для обработанного изображения и инструкции для направления этого сообщения Стороне A
- Messenger отправляет Стороне A сообщение от Стороны B, содержащее адрес обработанного изображения
- Сторона А считает свою работу выполненной, поскольку теперь она имеет ссылку на обработанное изображение.
- Когда-нибудь в будущем, когда Сторона А нуждается в изображении, она получает изображение, используя адрес (возможно, отправляя сообщения какой-либо другой стороне)
Вы видите проблему здесь? В обоих случаях, Сторона А не может получить адрес изображения , пока после того, как Сторона B обрабатывает изображение . Тем не менее, Сторона А не нуждается в изображении сразу, и, по праву, все равно, если обработка еще не завершена!
Мы можем легко исправить это в случае AMQP, если Сторона B сообщит A, что B приняла изображение для обработки, и предоставит A адрес, по которому изображение будет находиться после завершения обработки. Затем Сторона B может отправить A сообщение в будущем, указывающее, что обработка изображения завершена. Обмен сообщениями AMQP на помощь!
Кроме угадайте, что: вы можете достичь того же с помощью REST . В примере AMQP мы изменили сообщение «вот обработанное изображение» на сообщение «изображение обрабатывается, вы можете получить его позже». Чтобы сделать это в RESTful HTTP, мы будем использовать 202 Accepted
код и Content-Location
снова:
- Сторона A отправляет HTTP-
POST
сообщение стороне B сContent-Type: image/jpeg
- Сторона B немедленно отправляет обратно
202 Accepted
ответ, который содержит некоторый контент «асинхронной операции», который описывает, завершена ли обработка и где изображение будет доступно после завершения обработки. Также включен Content-Location: <link>
заголовок, который в 202 Accepted
ответе представляет собой ссылку на ресурс, представленный всем телом ответа. В данном случае это означает, что это ссылка на нашу асинхронную операцию!
- Сторона А считает свою работу выполненной, поскольку теперь она имеет ссылку на обработанное изображение.
- Когда-нибудь в будущем, когда Стороне А понадобится обработанное изображение, она сначала ПОЛУЧИТ ресурс асинхронной операции, связанный с
Content-Location
заголовком, чтобы определить, завершена ли обработка. Если это так, то Сторона А затем использует ссылку в самой асинхронной операции, чтобы ПОЛУЧИТЬ обработанное изображение.
Единственное отличие состоит в том, что в модели AMQP Сторона B сообщает Стороне A, когда обработка изображения завершена. Но в модели REST Сторона A проверяет, выполняется ли обработка непосредственно перед тем, как ей действительно потребуется изображение. Эти подходы эквивалентно масштабируемы . По мере увеличения системы количество сообщений, отправляемых как в асинхронных AMQP, так и в асинхронных REST-стратегиях, увеличивается с эквивалентной асимптотической сложностью. Разница лишь в том, что клиент отправляет дополнительное сообщение вместо сервера.
Но у подхода REST есть еще несколько хитростей: динамическое обнаружение и согласование протокола . Рассмотрим, как началось синхронное и асинхронное взаимодействие REST. Сторона A направила точно такой же запрос Стороне B, с той лишь разницей, что это был особый вид сообщения об успехе, на которое Сторона B ответила. Что, если Сторона А хотела выбрать, будет ли обработка изображения синхронной или асинхронной? Что, если Сторона A не знает, способна ли Сторона B даже к асинхронной обработке?
Ну, на самом деле HTTP уже имеет стандартизированный протокол для этого! Он называется HTTP Preferences, а именно respond-async
RFC 7240, раздел 4.1. Если Сторона A желает получить асинхронный ответ, она включает Prefer: respond-async
заголовок с начальным запросом POST. Если Сторона B решает удовлетворить этот запрос, она отправляет 202 Accepted
ответ, который включает в себя Preference-Applied: respond-async
. В противном случае Сторона B просто игнорирует Prefer
заголовок и отправляет обратно, 201 Created
как обычно.
Это позволяет Стороне А вести переговоры с сервером, динамически адаптируясь к любой реализации обработки изображений, с которой она общается. Кроме того, использование явных ссылок означает, что Сторона A не должна знать о каких-либо сторонах, кроме B: нет брокера сообщений AMQP, нет загадочной Стороны C, которая знает, как на самом деле преобразовать адрес изображения в данные изображения, нет второй B-Async участник, если необходимо сделать синхронные и асинхронные запросы и т. д. Он просто описывает, что ему нужно, что ему может потребоваться, а затем реагирует на коды состояния, содержимое ответа и ссылки. Добавить вCache-Control
заголовки для явных инструкций о том, когда хранить локальные копии данных, и теперь серверы могут согласовывать с клиентами, ресурсы каких клиентов могут хранить локальные (или даже автономные!) копии. Именно так вы строите слабосвязанные отказоустойчивые микросервисы в REST.