Асинхронное взаимодействие
1. Выбранный асинхронный процесс
В качестве асинхронного взаимодействия в системе выбран процесс обмена сообщениями в реальном времени между пользователями в обычном чате после успешного подтверждения владельца.
Логика процесса выглядит следующим образом:
-
Пользователь откликается на объявление.
-
Система создаёт диалог между сторонами.
-
До подтверждения владельца общение ограничено режимом owner-check.
-
После успешного подтверждения owner-check диалог переводится в режим обычного чата.
-
Именно этот этап, то есть обмен сообщениями в обычном чате рассматривается как асинхронное взаимодействие.
3. Почему этот процесс является асинхронным
Данный процесс является асинхронным, так как отправка и получение сообщений не укладываются в модель «один HTTP-запрос – один HTTP-ответ». После установки соединения стороны продолжают обмениваться данными в рамках постоянного канала связи.
Асинхронный характер взаимодействия проявляется в следующем:
-
отправитель сообщения не ждёт, пока второй пользователь выполнит какое-либо действие;
-
сообщение передаётся через постоянное соединение и доставляется отдельно от момента отправки;
-
сервер может самостоятельно отправлять события подключённым клиентам;
-
обмен сообщениями происходит в реальном времени и не требует постоянного ручного обновления страницы.
4. Обоснование выбора технологии
4.1. Требования процесса к технологии
Для реализации выбранного процесса технология должна обеспечивать:
-
двусторонний обмен данными между участниками;
-
получение новых сообщений без перезагрузки страницы;
-
быструю доставку сообщений;
-
поддержку нескольких событий в рамках одного соединения;
-
корректную работу в рамках веб-платформы.
4.2. Характер взаимодействия
В системе используется типичный web-стек, включающий frontend и backend. Для такого стека WebSocket естественно встраивается в слой клиент-серверного real-time взаимодействия. В качестве формата обмена используется JSON, так как он удобен для веб-разработки, легко обрабатывается на стороне клиента и сервера и хорошо подходит для описания событийных сообщений.
В рамках чата оба участника одновременно являются и отправителями, и получателями сообщений. Серверу необходимо не только принимать сообщения от клиента, но и самостоятельно доставлять события другим подключённым клиентам. По этой причине WebSocket лучше соответствует данному сценарию, чем обычный HTTP polling.
4.3. Почему именно WebSocket
Технология WebSocket выбрана по следующим причинам:
-
она поддерживает постоянное двустороннее соединение;
-
подходит для передачи сообщений в реальном времени;
-
естественно применяется в чатах;
-
хорошо соответствует веб-платформе;
-
позволяет использовать JSON и в дальнейшем описать контракт через AsyncAPI.
Следовательно, WebSocket является наиболее подходящей технологией.
5. Участники взаимодействия и компоненты системы
В выбранном процессе участвуют следующие компоненты:
-
Клиент пользователя A – интерфейс пользователя, который открывает чат, устанавливает соединение и отправляет сообщения;
-
Клиент пользователя B – интерфейс второго участника диалога, который получает сообщения и может отправлять ответные сообщения;
-
WebSocket Chat Gateway / сервер real-time соединений – компонент, который принимает WebSocket-подключения, поддерживает соединение и передаёт события между клиентами;
-
Chat Service – компонент бизнес-логики, который проверяет права доступа к диалогу, валидирует сообщения и управляет логикой чата;
-
База данных сообщений – хранилище, в котором сохраняются сообщения и состояние диалога.
Взаимодействие происходит не только между пользователями, но и между несколькими внутренними компонентами системы.
6. Сценарий взаимодействия
Ниже приведён базовый сценарий выбранного асинхронного взаимодействия:
-
Пользователь открывает экран чата.
-
Клиент устанавливает WebSocket-соединение с сервером.
-
Сервер проверяет право доступа пользователя к диалогу.
-
Пользователь отправляет сообщение.
-
Сервер принимает и валидирует сообщение.
-
Сообщение сохраняется в базе данных.
-
Сервер отправляет событие о новом сообщении второму участнику диалога.
-
Получатель видит сообщение без перезагрузки страницы.
Данный сценарий демонстрирует, что обмен данными происходит непрерывно в рамках одного соединения, а доставка сообщений осуществляется в реальном времени.
7. Контракт AsyncAPI
asyncapi: 3.1.0
info:
title: Lost&Found Chat WebSocket API
version: '1.0.0'
description: Контракт AsyncAPI для WebSocket-чата в системе Lost&Found.
defaultContentType: application/json
servers:
production:
host: api.lostfound.example.com
protocol: wss
description: Основной WebSocket-сервер чата
security:
- $ref: '#/components/securitySchemes/bearerAuth'
channels:
dialogChat:
address: /chat/{dialogId}
title: Канал чата по конкретному диалогу
parameters:
dialogId:
description: Идентификатор диалога
messages:
chatJoin:
$ref: '#/components/messages/chatJoin'
chatMessageSend:
$ref: '#/components/messages/chatMessageSend'
chatConnected:
$ref: '#/components/messages/chatConnected'
chatMessageReceived:
$ref: '#/components/messages/chatMessageReceived'
chatError:
$ref: '#/components/messages/chatError'
operations:
receiveChatJoin:
action: receive
channel:
$ref: '#/channels/dialogChat'
summary: Клиент сообщает серверу, что хочет начать работу с диалогом.
messages:
- $ref: '#/channels/dialogChat/messages/chatJoin'
receiveChatMessage:
action: receive
channel:
$ref: '#/channels/dialogChat'
summary: Клиент отправляет текстовое сообщение в диалог.
messages:
- $ref: '#/channels/dialogChat/messages/chatMessageSend'
sendChatConnected:
action: send
channel:
$ref: '#/channels/dialogChat'
summary: Сервер подтверждает успешное подключение клиента к чату.
messages:
- $ref: '#/channels/dialogChat/messages/chatConnected'
sendChatMessageReceived:
action: send
channel:
$ref: '#/channels/dialogChat'
summary: Сервер отправляет сохранённое сообщение участникам диалога, включая отправителя.
messages:
- $ref: '#/channels/dialogChat/messages/chatMessageReceived'
sendChatError:
action: send
channel:
$ref: '#/channels/dialogChat'
title: Отправка ошибки в активной сессии чата
summary: Сервер уведомляет клиента об ошибке обработки события.
messages:
- $ref: '#/channels/dialogChat/messages/chatError'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
messages:
chatJoin:
name: chat.join
title: Запрос на подключение к диалогу
payload:
$ref: '#/components/schemas/ChatJoinPayload'
chatMessageSend:
name: chat.message.send
title: Отправка сообщения в чат
payload:
$ref: '#/components/schemas/ChatMessageSendPayload'
chatConnected:
name: chat.connected
title: Подключение подтверждено
payload:
$ref: '#/components/schemas/ChatConnectedPayload'
chatMessageReceived:
name: chat.message.received
title: Сообщение доставлено участникам
payload:
$ref: '#/components/schemas/ChatMessageReceivedPayload'
chatError:
name: chat.error
title: Ошибка обработки события
summary: Сервер сообщает клиенту об ошибке в рамках активной сессии чата.
payload:
$ref: '#/components/schemas/ChatErrorPayload'
schemas:
ChatJoinPayload:
type: object
required:
- eventType
properties:
eventType:
type: string
const: chat.join
ChatMessageSendPayload:
type: object
required:
- eventType
- text
properties:
eventType:
type: string
const: chat.message.send
text:
type: string
minLength: 1
maxLength: 2000
ChatConnectedPayload:
type: object
required:
- eventType
- dialogId
- connectedAt
properties:
eventType:
type: string
const: chat.connected
dialogId:
type: string
format: uuid
connectedAt:
type: string
format: date-time
ChatMessageReceivedPayload:
type: object
required:
- eventType
- dialogId
- messageId
- senderId
- text
- sentAt
properties:
eventType:
type: string
const: chat.message.received
dialogId:
type: string
format: uuid
messageId:
type: string
format: uuid
senderId:
type: string
format: uuid
text:
type: string
minLength: 1
maxLength: 2000
sentAt:
type: string
format: date-time
ChatErrorPayload:
type: object
required:
- eventType
- code
- message
- sentAt
properties:
eventType:
type: string
const: chat.error
code:
type: string
enum:
- VALIDATION_ERROR
- INTERNAL_ERROR
message:
type: string
sentAt:
type: string
format: date-time