Недавно я подумывал о том, чтобы завести собаку, и во время своих поисков я понял, насколько было бы полезно получать уведомления о появлении новых животных в моем местном приюте. Я огляделся и, кажется, это особенность не многих приютов, если они вообще есть. Увидев, что проект выглядит забавным, я начал планировать решение в AWS.
Моя цель в этом проекте состояла в том, чтобы получить дополнительный опыт работы с JS, API и объединением функций Lambda. Я понимаю, что мое решение, включающее несколько Lambda, скорее всего, неэффективно, но меня больше интересовало получение некоторого опыта в этой области, чем создание идеального решения. Тем не менее, у меня есть идеи по улучшению, о которых я расскажу позже в этом посте.
Ссылка на Github: https://github.com/bdaley94/Shelter_Notification_Service
Это решение в настоящее время работает с 12.07.2023! Если вы живете в Хьюстоне, штат Техас (или просто любопытны), не стесняйтесь подписаться на уведомления о новых животных, которых можно усыновить, от Houston Pets Alive! на https://brandon-daley.com/HPA-Form. Обратите внимание, что в настоящее время работают только уведомления по электронной почте.
Обзор решения
Целью этого решения является предоставление обновлений по электронной почте пользователям, которые хотели бы знать, когда новые животные поступают в приют. Я использую функции Lambda, написанные на Python, для всей обработки. Существует база данных DynamoDB для кошек и собак, которую я использовал, чтобы отслеживать, какие животные были в приюте при последнем запуске кода. Пользователи подписываются на уведомления на https://brandon-daley.com/HPA-Form и выбирают, хотят ли они получать электронные письма о кошках, собаках или о том и другом. Пользователи могут отказаться от подписки, щелкнув ссылку в нижней части любого электронного письма, которое они получают. Цепочка Lambda заканчивается запросом к базе данных списка адресов электронной почты и отправкой электронного письма для каждого нового прибытия кошки/собаки в зависимости от того, на какие оповещения подписан пользователь.
1. Мост событий + лямбда
Мое решение начинается с правила Eventbridge, которое запускает цепочку Lambda каждый день в 19:00 по центральному времени. Первая лямбда-функция (Dog1/Cat1) отвечает за три вещи:
- Сбор текущей информации о доступных животных из стороннего API (Shelterluv), используемого приютом. Данные приходят в формате JSON.
- Сканирование соответствующей базы данных для получения списка животных, которые были доступны при последнем запуске решения.
- Сравните список животных на шаге 1 со списком на шаге 2. Животные, которые были в базе данных, но которых больше нет в вновь собранном списке из Shelterluv, были усыновлены. Животные, находящиеся в новом списке, но не в БД, являются вновь поступившими в приют.
Два словаря, один для новоприбывших и один для приемных животных, передаются Dog2/Cat2. Затем эта функция удаляет усыновленных животных и добавляет новых в соответствующую БД.
По завершении функции Dog2/Cat2 передают словарь, содержащий вновь прибывших животных, в Dog3/Cat3. Эта функция обрабатывает несколько вещей:
- Собирает список адресов электронной почты, на которые в настоящее время подписаны электронные письма для собак или кошек, в зависимости от того, какая функция запущена.
- Анализирует все соответствующие данные, которые потребуются пользователям в электронной почте, такие как встроенная фотография, порода, возраст, вес и т. д.
- Перебирает каждое новое поступившее животное и отправляет электронное письмо, содержащее указанную выше информацию, а также вставляет ссылку для отказа от подписки, предварительно заполненную адресом электронной почты получателя.
2. ДинамоДБ
Мое решение включает три таблицы DynamoDB. Есть два отдельных стола для собак и кошек. Эти таблицы используются для хранения информации о животных за предыдущий день. Это позволяет мне сравнивать современных животных с предыдущими и определять, какие животные являются новыми. Следует отметить, что данные о дне рождения, предоставляемые Shelterluv, представлены в формате времени Unix, поэтому моя функция Dog3/Cat3 включает код для преобразования их в удобочитаемые годы, месяцы и недели для отображения возраста животного. Ниже приведен пример данных, которые я храню.
В третьей таблице хранятся все адреса электронной почты, подписанные на уведомления, и сведения о животных, о которых они хотят получать электронные письма. Эта информация используется в Dog3/Cat3, чтобы определить, какие пользователи должны получать электронную почту собаки или кошки.
3. СЭС
Я использовал Simple Email Service для отправки уведомлений по электронной почте пользователям. Код находится в функции Dog3/Cat3. Я повторял каждое новое поступившее животное и отправлял электронное письмо для каждого животного каждому пользователю с запросом электронных писем для этого типа животных, прежде чем переходить к следующему животному, чтобы повторить эти шаги. Тело электронного письма представляет собой HTML, что позволило мне встроить изображение животного, а также разместить гиперссылки на страницу усыновления животного и на возможность отказа пользователя от подписки на будущие электронные письма.
Функциональность подписки
1. Веб-форма
Пользователь, желающий подписаться на эти уведомления, должен посетить https://brandon-daley.com/HPA-Form. Форма представляет собой простую веб-страницу, на которой пользователи могут указать свой адрес электронной почты и указать, какие уведомления они хотят получать. После отправки формы мой код Javascript запускается и отправляет данные формы на мой маршрут/форму API. Javascript также включает проверку действительных и полных данных формы, таких как проверка того, что пользователь заполняет все поля и отправляет действительный адрес электронной почты. Я начал создавать функциональность для SMS-уведомлений, как видно из этой формы, но в конечном итоге убрал эту функцию из-за новых правил, касающихся регистрации бесплатных номеров в США.
2. API_Sub — лямбда-функция
Вызов маршрута API /form запускает этот лямбда-код. Эта функция извлекает данные, отправленные формой, и загружает их в базу данных адресов электронной почты. Он также возвращает коды состояния, чтобы определить, была ли загрузка успешной, который затем будет использовать вышеупомянутый код JS, чтобы сообщить пользователю, была ли его информация успешно отправлена или нет. Он также сообщит пользователю, были ли обновлены предыдущие настройки уведомлений для отправленного адреса электронной почты.
Отписаться
1. Ссылка для отписки
Каждое уведомление по электронной почте, которое получает пользователь, будет содержать ссылку для отказа от подписки, которая предварительно заполнена адресом электронной почты пользователя для простоты использования. Все, что нужно сделать пользователю, это нажать на ссылку, и он будет отписан.
2. API_Unsub — лямбда-функция
Когда пользователь нажимает на ссылку отказа от подписки, он запускает этот лямбда-код. Код извлечет адрес электронной почты пользователя из строки запроса вызова API. Затем он проверит базу данных адресов электронной почты, чтобы подтвердить, что пользователь в настоящее время подписан. Если это подтвердится, они будут удалены из БД, и API вернет им подтверждающее сообщение. Если нет, они получат уведомление о том, что они в настоящее время не подписаны.
Безопасность
- Каждая лямбда-функция следует принципу наименьших привилегий в отношении разрешений для других служб.
- Форма API размещена на моем веб-сайте с соответствующей защитой HTTPS, которая шифрует данные, отправляемые через форму.
- Хранящиеся данные в DynamoDB полностью зашифрованы с помощью ключей, управляемых или принадлежащих AWS, через KMS.
- AWS SES по умолчанию использует оппортунистический TLS, что означает, что он «…всегда пытается установить безопасное соединение с принимающим почтовым сервером. Если Amazon SES не может установить безопасное соединение, он отправляет сообщение в незашифрованном виде».
Улучшения
Здесь я собираюсь перечислить некоторые вещи, которые я мог бы сделать, чтобы улучшить или расширить проект, когда вернусь к нему позже.
- Функции SMS-уведомлений. Это было бы значительным улучшением, и я бы хотел его добавить. Однако, как я кратко упомянул в другом месте этого блога, после попытки зарегистрировать номер телефона через AWS Pinpoint я обнаружил, что работа над этим проектом займет больше времени, чем я планировал. Несмотря на это, я разработал 99% требований для добавления этой функции, чтобы ее можно было легко добавить, все, что мне не хватает, это номер телефона.
- Использование альтернативных сервисов. Во время своего исследования я обнаружил, что один из способов отправки массовых электронных писем — это использование SQS. По сути, вы добавляете все адреса получателей в очередь, и ваша Lambda запускается для каждого сообщения в очереди и отправляет электронное письмо на этот адрес.
Проблема с этим заключается в том, что код, отправляющий электронные письма, должен иметь доступ к словарю новых животных от Dog2/Cat2, который в настоящее время передается Dog3/Cat3, который отправляет электронные письма с использованием этих данных. Поскольку отправка электронной почты Lambda будет запускаться каждым сообщением в очереди SQS, я не смогу передать эти данные. Я полагаю, что мог бы хранить словарь в виде JSON в каждом сообщении, но при этом отправляется много избыточных данных. Я считаю, что лучшим вариантом здесь было бы использовать другую таблицу DynamoDB в качестве эфемерного хранилища для хранения новых поступлений и получать из нее Lambda для отправки электронной почты при каждом вызове сообщения SQS. Я не смог найти хорошего способа определить, когда очередь пуста, без постоянного ее опроса, что было бы плохой практикой для снижения затрат. Лучшим вариантом здесь может быть его очистка в указанное время, скажем, через 30 минут после 19:00 по тихоокеанскому времени, с помощью правила Eventbridge, запускающего функцию Lambda. - Усовершенствованный пользовательский интерфейс для отказа от подписки. В настоящее время пользовательский интерфейс для отказа от подписки очень примитивен и не предлагает ожидаемых пользователем функций, таких как возможность повторной подписки, если вы случайно отмените подписку. Кроме того, в настоящее время он возвращает пустую страницу ответа непосредственно из API, содержащую только текст о том, была ли отмена подписки успешной или нет. Это выполняет свою работу, но его нужно немного украсить в производстве. Чтобы улучшить это, я бы создал простую веб-страницу, на которую пользователь будет отправлен вместо прямого URL-адреса API, и чтобы API возвращал ответ, который затем отображался пользователю.
- Инфраструктура как код. Как всегда, интеграция IAC в этот проект позволила бы мне разобрать его и повторно развернуть, а также упростить внесение изменений в конфигурацию.
- Объединяйте животных в электронные письма — если в приют придет много животных в один день, подписчики получат множество писем, потому что каждое животное получает индивидуальное письмо. Я мог бы реализовать некоторый код, чтобы проверить, есть ли в лямбда-функции Dog3/Cat3 более одной кошки или собаки, и если да, то объединить электронные письма в одно, прежде чем перебирать список адресов электронной почты и отправлять их.
💡 Это улучшение было реализовано и доступно с 12.07.2023!
- Электронное письмо для подтверждения подписки при регистрации. Вместо (или в дополнение) к моему JS-коду, который уведомляет пользователя об успешной подписке, я также могу отправить электронное письмо, чтобы уведомить его о том, что он зарегистрирован.