Создайте полностью масштабируемую облачную архитектуру с помощью Amazon Web Services
Несколько лет назад (до пандемии) мой клиент позвонил мне очень взволнованный, у него был большой проект, и он нуждался в моих услугах. Он сказал мне: «Алекс, я хочу сделать SuitePad для рынка Латинской Америки». Чего ждать?
Если вы путешествовали и отдыхали в роскошных отелях или на курортах, вы должны были использовать многофункциональный планшет или планшет в своем гостиничном номере. Это устройство в основном позволяет визуализировать информацию об отеле, телефонных номерах, меню, ценах и событиях, а иногда позволяет связываться с обслуживанием номеров или рецепцией. Отличным примером продукта такого типа является SuitePad.
И я, наконец, сделал это! Вот обзор финального приложения:
В этой статье я объясню, как построить такую платформу, используя Unity3D для визуального интерфейса и Amazon Web Services (AWS) для серверной архитектуры. В этой статье речь пойдет о том, как настроить сервисы и установить связь между ними. В целях конфиденциальности я покажу только фрагменты кода основных функций платформы.
Требования
Мой клиент утверждает, что он уже купил несколько планшетов Android и нуждался во мне для создания платформы. Первая доставка будет очень простой: она только загрузит информацию об отеле с сервера и покажет ее на планшете. Нет связи с обслуживанием номеров и другими функциями.
У моего клиента было много требований к первой поставке:
- Приложение должно быть «белой этикеткой»: оно может адаптироваться к любому отелю. Поэтому логотипы, цвета приложений, текст, мультимедиа и т. д. следует загружать из облака.
- Приложение должно иметь механизм входа в систему с именем пользователя и паролем. Имя пользователя должно быть уникальным для каждого гостиничного номера.
- Приложение должно быть мультиязычным и мультивалютным.
- Приложение должно работать как на устройствах Android, так и на iOS и учитывать горизонтальные и вертикальные форматы.
- Приложение должно загружать все доступные медиафайлы (изображения, тексты и т. д.) после того, как пользователь войдет в систему.
- Сервер должен обрабатывать большое количество клиентских подключений.
Архитектура программного обеспечения
Давай начнем.
Для клиента нам нужно создать приложение, которое может работать как на Android, так и на iOS. Я решил работать с Unity3D из-за его гибкости (приложения Unity могут работать на настольных компьютерах, iOS, Android, WebGL, tvOS, PS4 и PS5) и моих общих знаний о платформе.
Для серверной части нам нужна масштабируемая архитектура, построенная на облачной платформе. Я решил работать с Amazon Web Services (AWS) главным образом из-за надежности масштабируемых сервисов и моего опыта работы с ними.
Вот общая архитектура нашей платформы:
Примечание: я не буду внедрять AWS SDK; Я предпочитаю сохранять независимость между клиентом и сервером.
Серверная реализация
Когнито
Первый сервис Amazon, который мы используем, — Cognito. Cognito похож на сейф, в котором хранятся пользователи, атрибуты пользователей и пароли. Он обеспечивает безопасную аутентификацию пользователей с помощью имен пользователей и паролей.
➡️ Пул пользователей. Сначала мы создаем пул пользователей с отключенной аутентификацией по имени пользователя и самостоятельной регистрацией. Обратите внимание, что идентификатор пула пользователей будет сгенерирован автоматически.
➡️ Приложение: в процессе создания пула пользователей будет создано приложение пула пользователей. Мы выбрали аутентификацию по паролю пользователя в качестве единственного разрешенного потока аутентификации и создали два настраиваемых атрибута: hotel_name
и room_number
. Обратите внимание, что идентификатор клиента приложения будет сгенерирован автоматически.
➡️ Пользователи: мы создаем пользователей вручную в пуле пользователей. Поле имени пользователя уникально, поэтому я установил его как объединение названия отеля и номера комнаты.
РДС
RDS расшифровывается как служба реляционной базы данных. В реляционной базе данных мы будем хранить всю необходимую информацию о транзакциях.
➡️ Создание базы данных. Сначала мы создаем базу данных MySQL с открытым доступом и настраиваем учетные данные (имя базы данных, имя пользователя и пароль):
➡️ Правила безопасности: Теперь нам нужно получить доступ к базе данных, чтобы построить таблицы и заполнить их данными. Я решил сделать это с настольным MySQL-клиентом: Sequel Pro. Итак, нам нужно разрешить внешние подключения к нашей базе данных: мы входим в группу безопасности нашей базы данных и меняем правила для входящих подключений, чтобы разрешить все подключения.
Теперь мы можем подключиться к нашей базе данных из клиента MySQL с учетными данными, которые мы установили во время создания базы данных:
ДинамоДБ
DynamoDB — это нереляционная служба баз данных AWS. Мы будем хранить в нем всю имеющуюся у нас нетранзакционную информацию, такую как заголовки, описания, URL-адреса изображений и т. д.
➡️ Создание таблицы: мы создаем таблицу DynamoDB. Наши данные будут сегментированы по отелям, поэтому мы выбрали этот параметр в качестве Ключа раздела таблицы.
➡️ Создание элемента: мы создаем новый элемент, содержащий наши данные JSON.
S3
S3 — это масштабируемый сервис хранения статического контента, мы используем его для хранения картинок, которые будут отображаться на планшете.
➡️ Корзина: мы создаем новую корзину, также известную как репозиторий. Поскольку мы не храним конфиденциальные медиафайлы (изображения отелей, меню, номера и т. д. можно найти в Интернете), мы устанавливаем корзину и ее содержимое как «общедоступные».
➡️ Политика корзины. Мы редактируем политику корзины, чтобы разрешить только чтение содержимого корзины.
Примечание. В приведенном выше примере конечный URL-адрес нашего репозитория будет выглядеть так: https://hotelmotelbucket.s3.amazonaws.com/.
лямбда
Lambda — отличный бессерверный сервис, который позволяет запускать код, позволяющий Amazon управлять ресурсами сервера за вас.
➡️ Функция GetData. Эта функция Lambda позволяет получать данные об отелях, хранящиеся в DynamoDB. Во-первых, мы создаем новую функцию с нуля, работающую в последней версии Python, предоставленной Lambda (на данный момент 3.9).
Вот функция:
Примечания:
- Благодаря авторизатору, который мы используем, вся информация о пользователе будет доступна в переменной события. Мы можем получить значение пользовательского атрибута
hotel_name
, который мы определили в приложении пула пользователей. В нашем примере это значение должно быть «лотос». - Мы используем boto3, AWS SDK для Python, для вызова других сервисов AWS, в данном случае DynamoDB. Мы используем функцию
get_item
для получения данных из таблицы HotelMotel с ключом lotus. - Собственная библиотека Python JSON не поддерживает тип Decimal, поэтому мы создаем класс
DecimalEncoder
для преобразования Decimals в float. - Мы используем предложение try/except для обработки потенциальных ошибок.
По умолчанию функции Lambda не имеют разрешения на чтение или запись таблиц DynamoDB, поэтому не забудьте прикрепить разрешение AmazonDynamoDBReadOnlyAccess
к только что созданной функции Lambda.
➡️ Функция OnLogged: эта функция Lambda позволит сохранить новый логин пользователя в нашей реляционной базе данных. Эта функция будет вызываться непосредственно Cognito в качестве триггера, когда пользователь успешно входит в систему.
Таблица для сохранения истории входа в реляционную базу данных выглядит следующим образом:
Для записи в реляционную базу данных нам нужно сначала установить соединение с библиотекой PyMySQL. Проблема: PyMySQL не является собственной библиотекой Lambda, поэтому нам нужно создать новый слой и импортировать в него PyMySQL. Слой можно повторно использовать для других функций.
Примечание: чистую версию PyMySQL можно найти на PyPI.
Теперь мы можем добавить только что созданный слой для нашей функции:
И вот функция:
Примечания:
- Как описано в документации AWS, объект события содержит информацию о пользователе, такую как имя пользователя.
- Мы возвращаем весь объект события, как описано в документации.
Ну, а теперь вернемся к Cognito. Мы создаем новый триггер пост-аутентификации в нашем пользовательском пуле и назначаем только что созданную лямбда-функцию:
Шлюз API
Сервис API Gateway позволяет создать безопасную точку входа для нашего приложения и подключить его к другим используемым нами сервисам AWS, в данном случае к Lambda.
➡️ API: мы создаем новый REST API
➡️ Авторизатор: мы создаем новый авторизатор, чтобы разрешить только пользователям, принадлежащим к пулу пользователей, вызывать ресурсы API, которые мы создадим.
➡️ Ресурс: мы создаем новый ресурс. Имя ресурса будет суффиксом конечного URL точки входа.
➡️ Метод: нам нужно указать, как будет вызываться ресурс, создав новый метод. Мы создаем метод POST с интеграцией Lambda и выбираем ранее написанную функцию Python. Обратите внимание, что необходимо активировать интеграцию Lambda Proxy, чтобы разрешить отправку параметров из клиента Unity.
В методе, который мы только что создали, мы выбрали наш Authorizer:
➡️ Развертывание: наш API готов, нам просто нужно его развернуть. Создаем новый этап, например, «prod».
Наш API развернут и готов к использованию! Обратите внимание, что появившийся URL-адрес будет вызываться нашим клиентом Unity.
Давайте попробуем функцию получения данных нашего API с Почтальоном:
Это работает как шарм 🙌
UX/UI-дизайн
Поскольку я не дизайнер, я задавался вопросом, как я могу создать хорошо спроектированный продукт с учетом взаимодействия с пользователем, сочетания цветов, правильного размера шрифта и т. д. Для первой доставки я выбрал работу с адаптивным HTML. шаблон: Шаблон отеля Lotus.
Хорошо, я не использовал код HTML, я просто реплицировал дизайн мобильного шаблона в компоненты Unity UI (Canvas, Button, Image и т.д.)
Шаблон предлагает большое количество изображений и иконок. Добавив изображения с бесплатных сайтов-стоков изображений, таких как Pixabay, я мог получить отличное демо-приложение.
Unity3D-клиент
Войти в Когнито
Чтобы выполнить вход в Cognito, мы выполним почтовый запрос благодаря классу UnityWebRequest. Поскольку мы работаем с данными в формате JSON, мы используем функцию ToJson класса JsonUtility для подстраивания наших параметров входа в систему. Таким же образом мы используем функцию FromJson для разбора данных ответа.
Во-первых, мы создаем классы данных в соответствии со структурой, описанной в Документации Cognito, и используем атрибут Serializable для выполнения требований JsonUtility:
Затем мы вызываем службу Cognito с параметрами пользователя (имя пользователя и пароль) для входа в систему:
Примечания:
- Класс
UnityWebRequest
работает внутри сопрограммы. - Параметр
ClientId
— это идентификатор клиента приложения, сгенерированный Cognito. - URL-адрес для входа может быть другим, если вы выбрали другой регион для создания пула пользователей (us-east-1).
- Функция Post UnityWebRequest не поддерживает строки JSON и вместо этого использует странное шифрование строк HTML. Обходной путь — сделать странный трюк с байтовым массивом и запросом Put. Если у вас есть лучшее решение, сообщите мне об этом в разделе комментариев.
- Для правильной работы запроса необходимы заголовки Content-Type и X-Amz-Target.
- Мы будем использовать значение
IdToken
, возвращенное Cognito, в качестве параметра авторизации в нашей функции GetData.
Получить данные об отеле
Поскольку мы успешно вошли в Cognito, теперь мы можем получить данные об отеле, необходимые для отображения на экране.
Примечания:
- Мы используем URL-адрес точки входа, которую мы развернули в API Gateway.
- Мы отправляем IdToken, полученный от Cognito, в качестве заголовка запроса.
Загрузить изображение с S3
Мы зарегистрировались и получили данные, которые нам нужно показать на панели. Теперь мы можем загрузить носитель из репозитория, который мы создали на S3 (hotelmotelbucket
).
Примечания:
- Мы работаем с UI-компонентами, поэтому используем класс UI Image для показа картинок.
- Используем функцию GetTexture из класса UnityWebRequestTexture для загрузки картинки из репозитория.
- Класс UnityWebRequestTexture работает внутри Coroutine.
- Приводим входящие данные с классом DownloadHandlerTexture, чтобы получить объект Texture2D.
- Мы динамически создаем спрайт изображения пользовательского интерфейса с размером текстуры.
Постановка и удаление задач из очереди
Приложение Unity3D получает полные данные об отеле после того, как пользователь войдет в систему и загрузит доступные медиафайлы (изображения номеров, еды и т. д.) из репозитория. Проблема в следующем: медиа очень много и устройство, на котором запущено приложение, не справляется с сотнями одновременных загрузок изображений.
Обходной путь — создание механизма очереди с использованием методов C# Enqueue и Dequeue.
Примечания:
- Очередь, которую мы создаем, — это очередь сопрограмм: мы ставим в очередь и удаляем из очереди функцию
GetPicture
, которую мы создали ранее. - Когда сопрограмма удаляется из очереди, выполняется связанная с ней функция
GetPicture
.
Поддержка горизонтальных и вертикальных форматов
Мы работаем с адаптивными элементами пользовательского интерфейса, но горизонтальный и вертикальный форматы слишком отличаются друг от друга, чтобы считаться частью одного и того же дизайна, поэтому мы создаем два разных Canvas, по одному для каждого формата.
И мы используем класс Screen, чтобы показать адекватный Canvas:
Бонус: система часов
Приложение содержит часы, сделанные из текстовых объектов, вот код:
Примечания:
- Мы используем функцию InvokeRepeating, чтобы обновлять часы каждую минуту и звенеть точками каждые полсекунды.
- Мы используем функцию Now класса DateTime для получения фактического времени устройства.
- Каждая цифра управляется отдельно, чтобы иметь больший контроль над ней.
Заключительные мысли
В этой статье показано, как построить масштабируемую облачную архитектуру с помощью Amazon Web Services, как обеспечить взаимодействие этих служб и как приложение Unity3D может взаимодействовать с этими облачными службами.
Каждый код в этой статье был протестирован с использованием Unity 2020.3.17 и Visual Studio Community 2019. Мобильное устройство, которое я использовал для запуска приложения Unity, — это Galaxy Tab A7 Lite с Android 11.
Все идентификаторы и токены, показанные в этой статье, являются поддельными или просроченными, если вы попытаетесь их использовать, вы не сможете установить какие-либо соединения.
Особая благодарность Gianca Chavest за создание прекрасных иллюстраций.