Создание пользовательских уведомлений с помощью AWS WebSockets

Предыдущие части: Часть 1, Часть 2, Часть 3

Когда вы авторизуетесь на сайтах вроде Facebook, то куда первым делом падает ваш взгляд? На верхнюю часть новостной ленты? На баннер сторис?

Вряд ли.

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

Каждый мелкий значок, который мы видим, запускает у нас в голове сигнал, которые придает нам чувство удовлетворения. Честно говоря, мы зависимы от уведомлений. Поэтому, что может быть лучше, чем реализация такой функции с помощью WebSocket?

Развертывание в облако

Так же, как и в трех предыдущих частях, вся работа уже проделана заранее. Для развертывания обновлений, добавляющих пользовательские уведомления, выполните следующую команду из терминала в корне репозитория на локальной машине:

git fetch 
git checkout part-four
npm run deploy

Так вы переключитесь на текущую часть проекта и развернете его в AWS. Для поддержки пользовательских уведомлений были добавлены следующие дополнения:

  • лямбда-функция для поиска подключений конкретных пользователей и отправки им уведомлений;
  • очередь зависших сообщений (DLQ) для сбоев доставки/обработки;
  • оповещение CloudWatch для уведомлений о сбое;
  • обновления в нашей спецификации Async API, которые определяют новые входящие и исходящие события.

К счастью, добавление пользовательских уведомлений к уже созданной нами основе оказалось несложным. Наша структура данных позволяет получать соединения из схемы доступа конкретного пользователя, поэтому изменения оказались простыми.

Поиск пользовательского подключения

Используя WebSocket, созданный в позапрошлой статье, мы добавили в конечную точку $connect лямбда-авторизатор, сохраняющий в базе данных userId подключающегося пользователя.

Теперь с помощью этой информации мы можем отыскать открытые для пользователя соединения, чтобы отправлять ему push-уведомления. Когда поступит новое событие EventBridge Send User Push Notification, мы сможем запросить в базе данных все соединения этого UserId и отправить ему сообщение.

Использование деталей подключения для получения push-уведомления

В нашей модели данных мы сохраняем ID пользователя в pk в нашем GSI, что позволяет выполнять запрос только для ID, содержащегося во входящем событии.

{ 
"pk": "<connectionId>",
"sk": "connection#",
"GSI1PK": "<userId>",
"GSI1SK": "user#",
"ipAddress": "<connecting ip address>",
"connectedAt": "<epoch connected at time>",
"ttl": "<time to live before connection is removed>"
}

Глобальный вторичный индекс (GSI) позволяет нам запрашивать все записи базы данных, содержащие GSI1PK нужного userId. Так мы получаем подключения к WebSocket, по которым сможем отправлять сообщение от события. Смотрите лямбда-функцию.

Типы пользовательских уведомлений

С помощью нашего соединения WebSocket мы можем определить два типа уведомлений пользователя: активные и фидбэк-уведомления. Каждый тип служит своей уникальной цели.

Активные уведомления

Если при клике по уведомлению в Facebook оно в ответ ничего не делает, значит и особого интереса у нас такие события не вызовут. А вот если вы кликните по нему и будете переадресованы для выполнения некоего действия, то такое уведомление окажется ценнее. 

Push-уведомления, которые мы отправляем пользователям через WebSocket, содержат два элемента данных:

  • сообщение, которое нужно показать пользователю;
  • обратный вызов, по которому пользователь может проследовать для выполнения действия.

Сообщение будет информировать получателя о произошедшем, а обратный вызов позволит предпринять необходимое действие. Вот пример события:

{ 
"message": "The XYZ report has finished processing and is ready for review.",
"callback": "https://www.gopherholesunlimited.com/jobs/736ajdff7/results"
}

Прочитав это сообщение, пользователь поймет, что закончилась обработка запрошенного им отчета. Когда он перейдет по url callback, то увидит результаты, относящиеся к этому сообщению.

Фидбэк-уведомления

И напротив, фидбэк-уведомления лишь сообщают об обновлении состояния и не добавляют мелкий красный значок у иконки уведомлений в пользовательском интерфейсе.

Если вы ожидаете завершения длительного процесса, фидбэк-уведомление будет сообщать вам о том, на какой стадии он находится. В качестве примера можно привести процент завершения загрузки или сообщение о выполняемом действии. Если взять пример с отчетом, то подобный тип уведомления будет выглядеть так:

{ 
"message": "Calculating average time between status changes..."
}

Обратите внимание, что здесь нет callback. Это уведомление строго информативно и не предполагает каких-либо действий.

Задача фидбэк-уведомлений показывать конечному пользователю, что система чем-то занята.

Если пользователю напоминать о том, что некая фоновая работа продолжает свое выполнение, это избавит его от лишних переживаний по поводу возможного сбоя.

Обработка ошибок с помощью push-уведомлений

Событийно-ориентированные архитектуры (EDA) славятся своей сложностью, когда дело доходит до обработки ошибок. Поскольку мы используем бессерверные сервисы, то необходимость обширного наблюдения удваивается. 

Для отслеживания состояния WebSocket мы реализовали очереди зависших сообщений, что позволило в случае сбоев сбрасывать события в одном месте. Существует два вида проблем, с которыми мы можем столкнуться в WebSocket:

  1. Сбой доставки события.
  2. Сбой обработки события.

Сбои доставки событий происходят, когда EventBridge не может поместить событие в нашу очередь SQS, или когда оно не может быть передано из SQS в обрабатывающую лямбду. Это может произойти в следствии отключения системы или неверной конфигурации. Независимо от причины мы отправляем такие сбои в DLQ для мониторинга и последующего рассмотрения. Когда информация о сбое оказывается в DLQ, мы можем разобраться, что происходит в системе и попробовать исправить проблему.

Для отправки в DLQ мы обновляем правило EventBridge, чтобы при сбое доставки перенаправляться на эту очередь.

Сбои обработки событий происходят на уровне кода. Причиной может быть ошибка в самом коде, отсутствие в событии ожидаемых данных либо троттлинг или сбой вызываемого в коде сервиса. В этом случае мы также передаем подобные ошибки в DLQ.

Для передачи мы в случае сбоя перенаправляем события с помощью функционала Lambda destinations

Как узнать о наличии ошибок?

Отправка ошибок в DLQ  —  это одно, но как нам узнать, когда что-то требует нашего внимания?

Для этого мы реализовали оповещатель CloudWatch, который следит за DLQ. Каждый раз, когда в DLQ оказывает 1+ элементов, срабатывает тема SNS, уведомляя заинтересованные стороны о системном сбое. 

После очистки очереди зависших сообщений от всех элементов оповещатель выключается и продолжает ожидать очередного сбоя.

Что делать с ошибками в DLQ?

Когда в DLQ попадает ошибка, у нас есть два варианта: ручное действие или автоматический повтор. Для начала рекомендую придерживаться ручного решения, пока вы не привыкните к устранению проблем, связанных с событиями. Когда вы разберетесь с паттернами их решения, можно будет попробовать автоматизацию. 

Научившись определять сбои событий, которые можно повторить, и изучив способы их исправления, нужно начать выстраивать вокруг этого процесса инфраструктуру. У команды LEGO есть прекрасное видео о переотправке событий после сбоев в их системе.

Заключение

Пользовательские уведомления играют важную роль в любой системе. В каждом продукте поддержание связи с конечным пользователем играет важнейшую роль в качестве удерживающего фактора. Если приложение будет молча допускать сбои (или даже не допускать) при работе над любым длительным процессом, то пользователю потребуется углубленно изучать ситуацию, чтобы разобраться в происходящем.

При работе с WebSocket API добавление подобных уведомлений сложностей не вызывает. Теперь настал ваш черед реализовывать их в своем приложении. Можете включить их в свои ступенчатые функции AWS в качестве элемента рабочего потока либо сделать их точкой назначения при завершении асинхронной лямбда-функции.

В завершающей статье нашей серии про WebSocket вас ждет пример внедрения всей проделанной нами работы в ваше существующее приложение. Мы создали WebSocket, теперь пора начинать его использовать.

Рекомендую вам развернуть эту программу в вашем аккаунте AWS и поэкспериментировать с ней. На данный момент у нас получился готовый к продакшену микросервис WebSocket, так что можете смело начинать его использовать.

Успехов в написании кода!

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Allen Helton: How to Build User Notifications With AWS WebSockets

Предыдущая статьяЭлементы архитектуры веб-приложений
Следующая статьяГенерирование синтетических обучающих данных с поддержкой масштабирования для задач NLP с помощью T0PP