Создайте полностью масштабируемую облачную архитектуру с помощью 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 за создание прекрасных иллюстраций.