Создание бессерверных интерфейсных приложений с использованием Google Cloud Platform
Опубликовано: 2022-03-10В последнее время парадигма разработки приложений начала меняться от ручного развертывания, масштабирования и обновления ресурсов, используемых в приложении, к тому, чтобы полагаться на сторонних поставщиков облачных услуг, которые выполняют большую часть управления этими ресурсами.
Как разработчик или организация, которая хочет создать приложение, соответствующее рынку, в кратчайшие сроки, вы можете сосредоточить основное внимание на предоставлении основного сервиса приложения своим пользователям, тратя при этом меньше времени на настройку, развертывание и стресс-тестирование. ваше приложение. Если это ваш вариант использования, обработка бизнес-логики вашего приложения бессерверным способом может быть вашим лучшим вариантом. Но как?
Эта статья полезна для фронтенд-инженеров, которые хотят создать определенные функции в своем приложении, или для серверных инженеров, которые хотят извлекать и обрабатывать определенные функции из существующей серверной службы с помощью бессерверного приложения, развернутого на Google Cloud Platform.
Примечание . Чтобы извлечь пользу из того, что здесь будет описано, вам необходимо иметь опыт работы с React. Опыт работы с бессерверными приложениями не требуется.
Прежде чем мы начнем, давайте разберемся, что на самом деле представляют собой бессерверные приложения и как можно использовать бессерверную архитектуру при создании приложения в контексте фронтенд-инженера.
Бессерверные приложения
Бессерверные приложения — это приложения, разбитые на крошечные многократно используемые функции, управляемые событиями, которые размещаются и управляются сторонними поставщиками облачных услуг в общедоступном облаке от имени автора приложения. Они запускаются определенными событиями и выполняются по требованию. Хотя суффикс « меньше », прикрепленный к слову serverless , указывает на отсутствие сервера, это не на 100% так. Эти приложения по-прежнему работают на серверах и других аппаратных ресурсах, но в этом случае эти ресурсы предоставляются не разработчиком, а сторонним поставщиком облачных услуг. Таким образом, они являются бессерверными для автора приложения, но по-прежнему работают на серверах и доступны через общедоступный Интернет.
Примером использования бессерверного приложения может быть отправка электронных писем потенциальным пользователям, которые посещают вашу целевую страницу и подписываются на получение электронных писем о запуске продукта. На данном этапе у вас, вероятно, еще нет работающей серверной службы, и вы не хотели бы жертвовать временем и ресурсами, необходимыми для ее создания, развертывания и управления, и все потому, что вам нужно отправлять электронные письма. Здесь вы можете написать один файл, который использует почтовый клиент, и развернуть его у любого облачного провайдера, который поддерживает бессерверное приложение, и позволить ему управлять этим приложением от вашего имени, пока вы подключаете это бессерверное приложение к своей целевой странице.
Хотя существует множество причин, по которым вы можете рассмотреть возможность использования бессерверных приложений или функций как услуги (FAAS), как их называют, для вашего внешнего приложения, вот несколько очень важных причин, которые вы должны учитывать:
- Автоматическое масштабирование приложения
Бессерверные приложения масштабируются по горизонтали, и это « масштабирование » автоматически выполняется облачным провайдером в зависимости от количества вызовов, поэтому разработчику не нужно вручную добавлять или удалять ресурсы, когда приложение находится под большой нагрузкой. - Экономическая эффективность
Будучи управляемыми событиями, бессерверные приложения запускаются только тогда, когда это необходимо, и это отражается на расходах, поскольку они выставляются в зависимости от количества вызовов времени. - Гибкость
Бессерверные приложения предназначены для многократного использования, а это означает, что они не привязаны к одному проекту или приложению. Определенную функциональность можно извлечь в бессерверное приложение, развернуть и использовать в нескольких проектах или приложениях. Бессерверные приложения также могут быть написаны на предпочитаемом автором языке, хотя некоторые облачные провайдеры поддерживают лишь меньшее количество языков.
При использовании бессерверных приложений у каждого разработчика есть множество облачных провайдеров в общедоступном облаке, к которым он может обратиться. В контексте этой статьи мы сосредоточимся на бессерверных приложениях на платформе Google Cloud — как они создаются, управляются, развертываются и как они также интегрируются с другими продуктами в Google Cloud. Для этого мы добавим новые функции в это существующее приложение React, работая над процессом:
- Хранение и получение данных пользователя в облаке;
- Создание и управление заданиями cron в Google Cloud;
- Развертывание облачных функций в облаке Google.
Примечание . Бессерверные приложения не привязаны только к React, если предпочитаемый интерфейсный фреймворк или библиотека могут выполнять HTTP
-запросы, они могут использовать бессерверное приложение.
Облачные функции Google
Облако Google позволяет разработчикам создавать бессерверные приложения с использованием облачных функций и запускать их с помощью фреймворка функций. Как они называются, облачные функции — это управляемые событиями функции многократного использования, развернутые в Google Cloud для прослушивания определенного триггера из шести доступных триггеров событий, а затем выполнения операции, для выполнения которой она была написана.
Облачные функции, которые недолговечны ( с временем ожидания выполнения по умолчанию 60 секунд и максимум 9 минут ), могут быть написаны с использованием JavaScript, Python, Golang и Java и выполняться с использованием их среды выполнения. В JavaScript они могут быть выполнены с использованием только некоторых доступных версий среды выполнения Node и написаны в виде модулей CommonJS с использованием простого JavaScript, поскольку они экспортируются в качестве основной функции для запуска в облаке Google.
Примером облачной функции является приведенная ниже, которая представляет собой пустой шаблон функции для обработки данных пользователя.
// index.js exports.firestoreFunction = function (req, res) { return res.status(200).send({ data: `Hello ${req.query.name}` }); }
Выше у нас есть модуль, который экспортирует функцию. При выполнении он получает аргументы запроса и ответа, аналогичные HTTP
-маршруту.
Примечание . Облачная функция сопоставляется с каждым протоколом HTTP
при выполнении запроса. На это стоит обратить внимание при ожидании данных в аргументе запроса, поскольку данные, прикрепленные при выполнении запроса на выполнение облачной функции, будут присутствовать в теле запроса для запросов POST
, а в теле запроса для запросов GET
.
Облачные функции можно выполнять локально во время разработки, установив пакет @google-cloud/functions-framework
в ту же папку, где находится написанная функция, или выполнив глобальную установку, чтобы использовать ее для нескольких функций, запустив npm i -g @google-cloud/functions-framework
из вашей командной строки. После установки его следует добавить в скрипт package.json
с именем экспортируемого модуля, подобным приведенному ниже:
"scripts": { "start": "functions-framework --target=firestoreFunction --port=8000", }
Выше у нас есть одна команда в наших сценариях в файле package.json
, которая запускает инфраструктуру функций, а также указывает firestoreFunction
в качестве целевой функции для локального запуска на порту 8000
.
Мы можем проверить конечную точку этой функции, отправив запрос GET
на порт 8000
на локальном хосте с помощью curl. Вставка приведенной ниже команды в терминал сделает это и вернет ответ.
curl https://localhost:8000?name="Smashing Magazine Author"
Приведенная выше команда делает запрос с помощью GET HTTP
и отвечает кодом состояния 200
и данными объекта, содержащими имя, добавленное в запросе.
Развертывание облачной функции
Из доступных методов развертывания одним из быстрых способов развертывания облачной функции с локального компьютера является использование облачного SDK после его установки. Выполнение приведенной ниже команды из терминала после аутентификации gcloud sdk с вашим проектом в облаке Google приведет к развертыванию локально созданной функции в службе облачных функций.
gcloud functions deploy "demo-function" --runtime nodejs10 --trigger-http --entry-point=demo --timeout=60 --set-env-vars=[name="Developer"] --allow-unauthenticated
Используя описанные ниже флаги, приведенная выше команда развертывает запускаемую HTTP-функцию в облаке Google с именем « демо-функция ».
- ИМЯ
Это имя, которое дается облачной функции при ее развертывании и является обязательным. -
region
Это регион, в котором должна быть развернута облачная функция. По умолчанию он развернут наus-central1
. -
trigger-http
Это выбирает HTTP в качестве типа триггера функции. -
allow-unauthenticated
Это позволяет вызывать функцию за пределами облака Google через Интернет с использованием сгенерированной конечной точки без проверки подлинности вызывающей стороны. -
source
Локальный путь от терминала к файлу, содержащему функцию, которую необходимо развернуть. -
entry-point
Это конкретный экспортированный модуль, который нужно развернуть из файла, в котором были написаны функции. -
runtime
Это среда выполнения языка, которая будет использоваться для функции из этого списка допустимых сред выполнения. -
timeout
Это максимальное время, в течение которого функция может работать до истечения времени ожидания. По умолчанию оно составляет 60 секунд, и его можно установить максимум на 9 минут.
Примечание . Если функция разрешает неаутентифицированные запросы, это означает, что любой, у кого есть конечная точка вашей функции, также может делать запросы без вашего разрешения. Чтобы смягчить это, мы можем убедиться, что конечная точка остается частной, используя ее через переменные среды или запрашивая заголовки авторизации при каждом запросе.
Теперь, когда наша демо-функция развернута и у нас есть конечная точка, мы можем протестировать эту функцию, как если бы она использовалась в реальном приложении с помощью глобальной установки autocannon. Запуск autocannon -d=5 -c=300 CLOUD_FUNCTION_URL
из открытого терминала сгенерирует 300 одновременных запросов к облачной функции в течение 5 секунд. Этого более чем достаточно, чтобы запустить облачную функцию, а также сгенерировать некоторые показатели, которые мы можем изучить на панели инструментов функции.
Примечание . Конечная точка функции будет распечатана в терминале после развертывания. Если это не так, запустите gcloud function describe FUNCTION_NAME
из терминала, чтобы получить сведения о развернутой функции, включая конечную точку.
Используя вкладку метрик на панели инструментов, мы можем увидеть визуальное представление последнего запроса, состоящее из того, сколько вызовов было сделано, как долго они длились, объем памяти функции и сколько экземпляров было запущено для обработки сделанных запросов.
Более пристальный взгляд на диаграмму Active Instances на изображении выше показывает возможности горизонтального масштабирования облачных функций, поскольку мы видим, что 209 экземпляров были запущены в течение нескольких секунд для обработки запросов, сделанных с помощью autocannon.
Журналы облачных функций
У каждой функции, развернутой в облаке Google, есть журнал, и каждый раз, когда эта функция выполняется, в этот журнал вносится новая запись. На вкладке « Журнал » на панели инструментов функции мы можем увидеть список всех записей журналов из облачной функции.
Ниже приведены записи журнала из нашей развернутой demo-function
, созданные в результате запросов, которые мы сделали с помощью autocannon
.
Каждая из приведенных выше записей журнала точно показывает, когда была выполнена функция, сколько времени заняло выполнение и каким кодом состояния оно завершилось. Если в результате функции возникают какие-либо ошибки, подробности ошибки, включая строку, в которой она произошла, будут показаны в журналах здесь.
Logs Explorer в Google Cloud можно использовать для просмотра более полных сведений о журналах из облачной функции.
Облачные функции с интерфейсными приложениями
Облачные функции очень полезны и эффективны для интерфейсных инженеров. Интерфейсный инженер, не обладающий знаниями об управлении внутренними приложениями, может извлечь функциональность в облачную функцию, развернуть ее в Google Cloud и использовать во внешнем приложении, отправив HTTP
-запросы к облачной функции через ее конечную точку.
Чтобы показать, как облачные функции можно использовать во внешнем приложении, мы добавим дополнительные функции в это приложение React. Приложение уже имеет базовую маршрутизацию между настройкой аутентификации и домашних страниц. Мы расширим его, чтобы использовать React Context API для управления состоянием нашего приложения, поскольку использование созданных облачных функций будет выполняться в редюсерах приложений.
Для начала мы создаем контекст нашего приложения с помощью API createContext
, а также создаем редьюсер для обработки действий в нашем приложении.
// state/index.js import { createContext } from “react”;
export const UserReducer = (действие, состояние) => { switch (action.type) { case «CREATE-USER»: break; случай «ЗАГРУЗИТЬ-ПОЛЬЗОВАТЕЛЬСКОЕ-ИЗОБРАЖЕНИЕ»: перерыв; case «FETCH-DATA»: разрыв case «LOGOUT»: перерыв; по умолчанию: console.log(
${action.type} is not recognized
) } };экспортировать const userState = {пользователь: null, isLoggedIn: false};
экспортировать const UserContext = createContext(userState);
Выше мы начали с создания функции UserReducer
, которая содержит оператор switch, позволяющий выполнять операцию в зависимости от типа отправленного в нее действия. Оператор switch имеет четыре случая, и это действия, которые мы будем обрабатывать. На данный момент они еще ничего не делают, но когда мы начнем интегрироваться с нашими облачными функциями, мы будем постепенно реализовывать действия, которые должны выполняться в них.
Мы также создали и экспортировали контекст нашего приложения с помощью React createContext API и присвоили ему значение по умолчанию для объекта userState
, который в настоящее время содержит пользовательское значение, которое будет обновлено с нулевого до данных пользователя после аутентификации, а также логическое значение isLoggedIn
, чтобы знать, если пользователь вошел в систему или нет.
Теперь мы можем приступить к использованию нашего контекста, но прежде чем мы это сделаем, нам нужно обернуть все наше дерево приложения Provider, прикрепленным к UserContext
, чтобы дочерние компоненты могли подписаться на изменение значения нашего контекста.
// index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./app"; import { UserContext, userState } from "./state/"; ReactDOM.render( <React.StrictMode> <UserContext.Provider value={userState}> <App /> </UserContext.Provider> </React.StrictMode>, document.getElementById("root") ); serviceWorker.unregister();
Мы обертываем наше приложение ввода провайдером UserContext
в корневом компоненте и передаем наше ранее созданное значение по умолчанию userState
в свойстве value.
Теперь, когда состояние нашего приложения полностью настроено, мы можем перейти к созданию модели данных пользователя с помощью Google Cloud Firestore через облачную функцию.
Обработка данных приложения
Данные пользователя в этом приложении состоят из уникального идентификатора, адреса электронной почты, пароля и URL-адреса изображения. Используя облачную функцию, эти данные будут храниться в облаке с помощью службы Cloud Firestore, которая предлагается на облачной платформе Google.
Google Cloud Firestore , гибкая база данных NoSQL, была создана из базы данных Firebase Realtime с новыми расширенными функциями, которые позволяют выполнять более сложные и быстрые запросы наряду с поддержкой данных в автономном режиме. Данные в службе Firestore организованы в коллекции и документы, аналогичные другим базам данных NoSQL, таким как MongoDB.
Доступ к Firestore можно получить визуально через Google Cloud Console. Чтобы запустить его, откройте левую панель навигации, прокрутите вниз до раздела «База данных» и нажмите «Firestore». Это покажет список коллекций для пользователей с существующими данными или предложит пользователю создать новую коллекцию, когда существующей коллекции нет. Мы создадим коллекцию пользователей , которая будет использоваться нашим приложением.
Подобно другим сервисам на облачной платформе Google, Cloud Firestore также имеет клиентскую библиотеку JavaScript, созданную для использования в среде узла ( при использовании в браузере будет выдана ошибка ). Чтобы импровизировать, мы используем Cloud Firestore в облачной функции с помощью пакета @google-cloud/firestore
.
Использование облачного хранилища Firestore с облачной функцией
Для начала мы переименуем первую созданную нами функцию из demo-function
в firestoreFunction
, а затем расширим ее, чтобы соединиться с Firestore и сохранить данные в коллекции наших пользователей.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const { SecretManagerServiceClient } = require("@google-cloud/secret-manager"); const client = new SecretManagerServiceClient(); exports.firestoreFunction = function (req, res) { return { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); console.log(document) // prints details of the collection to the function logs if (!type) { res.status(422).send("An action type was not specified"); } switch (type) { case "CREATE-USER": break case "LOGIN-USER": break; default: res.status(422).send(`${type} is not a valid function action`) } };
Чтобы обрабатывать больше операций, связанных с fire-store, мы добавили оператор switch с двумя случаями для обработки потребностей аутентификации нашего приложения. Наш оператор switch оценивает выражение type
, которое мы добавляем в тело запроса при выполнении запроса к этой функции из нашего приложения, и всякий раз, когда данные этого type
отсутствуют в нашем теле запроса, запрос идентифицируется как неверный запрос и код состояния 400
. вместе с сообщением, указывающим, что отсутствующий type
отправляется в качестве ответа.
Мы устанавливаем соединение с Firestore, используя библиотеку учетных данных приложения по умолчанию (ADC) в клиентской библиотеке Cloud Firestore. В следующей строке мы вызываем метод коллекции в другой переменной и передаем имя нашей коллекции. Мы будем использовать это для дальнейшего выполнения других операций с коллекцией содержащихся документов.
Примечание . Клиентские библиотеки для служб в Google Cloud подключаются к соответствующей службе с помощью созданного ключа учетной записи службы, который передается при инициализации конструктора. Если ключ учетной записи службы отсутствует, по умолчанию используются учетные данные приложения по умолчанию, которые, в свою очередь, подключаются с использованием ролей IAM
, назначенных облачной функции.
После редактирования исходного кода функции, которая была развернута локально с помощью Gcloud SDK, мы можем повторно запустить предыдущую команду из терминала, чтобы обновить и повторно развернуть облачную функцию.
Теперь, когда соединение установлено, мы можем реализовать случай CREATE-USER
для создания нового пользователя, используя данные из тела запроса.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const path = require("path"); const { v4 : uuid } = require("uuid") const cors = require("cors")({ origin: true }); const client = new SecretManagerServiceClient(); exports.firestoreFunction = function (req, res) { return cors(req, res, () => { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); if (!type) { res.status(422).send("An action type was not specified"); } switch (type) { case "CREATE-USER": if (!email || !password) { res.status(422).send("email and password fields missing"); } const id = uuid() return bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(password, salt, (err, hash) => { document.doc(id) .set({ id : id email: email, password: hash, img_uri : null }) .then((response) => res.status(200).send(response)) .catch((e) => res.status(501).send({ error : e }) ); }); }); case "LOGIN": break; default: res.status(400).send(`${type} is not a valid function action`) } }); };
Мы сгенерировали UUID, используя пакет uuid, который будет использоваться в качестве идентификатора документа, который нужно сохранить, передав его в метод set
документа, а также идентификатор пользователя. По умолчанию для каждого вставленного документа генерируется случайный идентификатор, но в этом случае мы будем обновлять документ при обработке загрузки изображения, а UUID — это то, что будет использоваться для обновления конкретного документа. Вместо того, чтобы хранить пароль пользователя в виде обычного текста, мы сначала используем его, используя bcryptjs, а затем сохраняем хэш результата в качестве пароля пользователя.
Интегрируя облачную функцию firestoreFunction
в приложение, мы используем ее из случая CREATE_USER
в пользовательском редюсере.
После нажатия кнопки « Создать учетную запись » редукторам с типом CREATE_USER
, чтобы сделать запрос POST
, содержащий введенный адрес электронной почты и пароль, к конечной точке функции firestoreFunction
.
import { createContext } from "react"; import { navigate } from "@reach/router"; import Axios from "axios"; export const userState = { user : null, isLoggedIn: false, }; export const UserReducer = (state, action) => { switch (action.type) { case "CREATE_USER": const FIRESTORE_FUNCTION = process.env.REACT_APP_FIRESTORE_FUNCTION; const { userEmail, userPassword } = action; const data = { type: "CREATE-USER", email: userEmail, password: userPassword, }; Axios.post(`${FIRESTORE_FUNCTION}`, data) .then((res) => { navigate("/home"); return { ...state, isLoggedIn: true }; }) .catch((e) => console.log(`couldnt create user. error : ${e}`)); break; case "LOGIN-USER": break; case "UPLOAD-USER-IMAGE": break; case "FETCH-DATA" : break case "LOGOUT": navigate("/login"); return { ...state, isLoggedIn: false }; default: break; } }; export const UserContext = createContext(userState);
Выше мы использовали Axios, чтобы сделать запрос к firestoreFunction
, и после того, как этот запрос был разрешен, мы устанавливаем начальное состояние пользователя с null
на данные, возвращенные из запроса, и, наконец, мы направляем пользователя на домашнюю страницу как аутентифицированный пользователь .
На этом этапе новый пользователь может успешно создать учетную запись и перейти на домашнюю страницу. Этот процесс демонстрирует, как мы используем Firestore для выполнения базового создания данных из облачной функции.
Работа с файловым хранилищем
Хранение и извлечение пользовательских файлов в приложении в большинстве случаев является очень необходимой функцией в приложении. В приложении, подключенном к серверной части node.js, Multer часто используется в качестве промежуточного программного обеспечения для обработки multipart/form-data, которые входят в загруженный файл. Но в отсутствие серверной части node.js мы могли бы использовать онлайн-файл. служба хранения, такая как Google Cloud Storage, для хранения статических активов приложений.
Облачное хранилище Google — это глобально доступная служба хранения файлов, используемая для хранения любого объема данных в виде объектов для приложений в корзинах. Он достаточно гибкий, чтобы обрабатывать статические ресурсы как для небольших, так и для крупных приложений.
Чтобы использовать службу облачного хранилища в приложении, мы могли бы использовать доступные конечные точки API хранилища или использовать официальную клиентскую библиотеку хранилища узла. Однако клиентская библиотека Node Storage не работает в окне браузера, поэтому мы можем использовать облачную функцию, где мы будем использовать библиотеку.
Примером этого является облачная функция ниже, которая подключает и загружает файл в созданную облачную корзину.
const cors = require("cors")({ origin: true }); const { Storage } = require("@google-cloud/storage"); const StorageClient = new Storage(); exports.Uploader = (req, res) => { const { file } = req.body; StorageClient.bucket("TEST_BUCKET") .file(file.name) .then((response) => { console.log(response); res.status(200).send(response) }) .catch((e) => res.status(422).send({error : e})); }); };
Из приведенной выше облачной функции мы выполняем две следующие основные операции:
Сначала мы создаем подключение к облачному хранилищу в
Storage constructor
, и он использует функцию учетных данных приложения по умолчанию (ADC) в облаке Google для аутентификации в облачном хранилище.Во-вторых, мы загружаем файл, включенный в тело запроса, в наш
TEST_BUCKET
, вызывая метод.file
и передавая имя файла. Поскольку это асинхронная операция, мы используем обещание, чтобы узнать, когда это действие было разрешено, и отправляем ответ200
, тем самым завершая жизненный цикл вызова.
Теперь мы можем расширить облачную функцию Uploader
выше, чтобы обрабатывать загрузку изображения профиля пользователя. Облачная функция получит изображение профиля пользователя, сохранит его в облачной корзине нашего приложения, а затем обновит данные img_uri
пользователя в коллекции наших пользователей в службе Firestore.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const cors = require("cors")({ origin: true }); const { Storage } = require("@google-cloud/storage"); const StorageClient = new Storage(); const BucketName = process.env.STORAGE_BUCKET exports.Uploader = (req, res) => { return Cors(req, res, () => { const { file , userId } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); StorageClient.bucket(BucketName) .file(file.name) .on("finish", () => { StorageClient.bucket(BucketName) .file(file.name) .makePublic() .then(() => { const img_uri = `https://storage.googleapis.com/${Bucket}/${file.path}`; document .doc(userId) .update({ img_uri, }) .then((updateResult) => res.status(200).send(updateResult)) .catch((e) => res.status(500).send(e)); }) .catch((e) => console.log(e)); }); }); };
Теперь мы расширили функцию загрузки выше, чтобы выполнять следующие дополнительные операции:
- Во-первых, он устанавливает новое подключение к службе Firestore, чтобы получить нашу коллекцию
users
, инициализируя конструктор Firestore, и использует учетные данные приложения по умолчанию (ADC) для аутентификации в облачном хранилище. - После загрузки файла, добавленного в тело запроса, мы делаем его общедоступным, чтобы к нему можно было получить доступ через общедоступный URL-адрес, вызвав метод
makePublic
для загруженного файла. В соответствии с управлением доступом по умолчанию в облачном хранилище, без публикации файла к нему нельзя получить доступ через Интернет и сделать это при загрузке приложения.
Примечание . Открытие файла означает, что любой, кто использует ваше приложение, может скопировать ссылку на файл и получить неограниченный доступ к файлу. Один из способов предотвратить это — использовать подписанный URL-адрес для предоставления временного доступа к файлу в вашей корзине вместо того, чтобы делать его полностью общедоступным.
- Затем мы обновляем существующие данные пользователя, чтобы включить URL-адрес загруженного файла. Мы находим данные конкретного пользователя с помощью запроса
WHERE
Firestore и используемuserId
, включенный в тело запроса, затем мы устанавливаем полеimg_uri
, чтобы оно содержало URL-адрес недавно обновленного изображения.
Описанную выше функцию « Upload
в облако» можно использовать в любом приложении, имеющем зарегистрированных пользователей в службе Firestore. Все, что нужно, чтобы сделать POST
-запрос к конечной точке, поместив ИС пользователя и изображение в тело запроса.
Примером этого в приложении является случай UPLOAD-FILE
, который делает запрос POST
к функции и помещает ссылку на изображение, возвращенную из запроса, в состояние приложения.
# index.js import Axios from 'axios' const UPLOAD_FUNCTION = process.env.REACT_APP_UPLOAD_FUNCTION export const UserReducer = (state, action) => { switch (action.type) { case "CREATE-USER" : # .....CREATE-USER-LOGIC .... case "UPLOAD-FILE": const { file, id } = action return Axios.post(UPLOAD_FUNCTION, { file, id }, { headers: { "Content-Type": "image/png", }, }) .then((response) => {}) .catch((e) => console.log(e)); default : return console.log(`${action.type} case not recognized`) } }
Из приведенного выше случая переключения мы делаем запрос POST
, используя Axios, для передачи UPLOAD_FUNCTION
добавленного файла, который будет включен в тело запроса, и мы также добавили Content-Type
изображения в заголовок запроса.
После успешной загрузки ответ, возвращаемый облачной функцией, содержит документ данных пользователя, который был обновлен, чтобы содержать действительный URL-адрес изображения, загруженного в облачное хранилище Google. Затем мы можем обновить состояние пользователя, чтобы оно содержало новые данные, и это также обновит элемент src
изображения профиля пользователя в компоненте профиля.
Обработка заданий Cron
Повторяющиеся автоматизированные задачи, такие как отправка электронных писем пользователям или выполнение внутренних действий в определенное время, в большинстве случаев являются включенной функцией приложений. В обычном приложении node.js такие задачи могут обрабатываться как задания cron с использованием node-cron или node-schedule. При создании бессерверных приложений с использованием облачной платформы Google Cloud Scheduler также предназначен для выполнения операции cron.
Примечание . Хотя Cloud Scheduler работает аналогично утилите cron Unix при создании заданий, которые будут выполняться в будущем, важно отметить, что Cloud Scheduler не выполняет команду, как это делает утилита cron. Скорее он выполняет операцию, используя указанную цель.
Как следует из названия, облачный планировщик позволяет пользователям планировать выполнение операции в будущем. Каждая операция называется заданием , и задания можно визуально создавать, обновлять и даже уничтожать в разделе «Планировщик» облачной консоли. Помимо поля имени и описания, задания в Cloud Scheduler состоят из следующего:
- Частота
Это используется для планирования выполнения задания Cron. Расписания указываются в формате unix-cron, который первоначально использовался при создании фоновых заданий в таблице cron в среде Linux. Формат unix-cron состоит из строки с пятью значениями, каждое из которых представляет момент времени. Ниже мы видим каждую из пяти строк и значения, которые они представляют.
- - - - - - - - - - - - - - - - minute ( - 59 ) | - - - - - - - - - - - - - hour ( 0 - 23 ) | | - - - - - - - - - - - - day of month ( 1 - 31 ) | | | - - - - - - - - - month ( 1 - 12 ) | | | | - - - - - -- day of week ( 0 - 6 ) | | | | | | | | | | | | | | | | | | | | | | | | | * * * * *
Инструмент генератора Crontab пригодится, когда вы пытаетесь сгенерировать значение частоты-времени для задания. Если вам сложно совместить значения времени, генератор Crontab имеет визуальное раскрывающееся меню, в котором вы можете выбрать значения, составляющие расписание, и скопировать сгенерированное значение и использовать его в качестве частоты.
- Часовой пояс
Часовой пояс, в котором выполняется задание cron. Из-за разницы во времени между часовыми поясами задания cron, выполняемые с разными указанными часовыми поясами, будут иметь разное время выполнения. - Цель
Это то, что используется при выполнении указанного задания. Целью может быть типHTTP
, когда задание делает запрос в указанное время к URL-адресу или теме Pub/Sub, в которой задание может публиковать сообщения или извлекать сообщения, и, наконец, приложение App Engine.
Облачный планировщик отлично сочетается с облачными функциями, активируемыми HTTP. Когда задание в Cloud Scheduler создается с целевым значением HTTP, это задание можно использовать для выполнения облачной функции. Все, что нужно сделать, это указать конечную точку облачной функции, указать HTTP-глагол запроса, а затем добавить все данные, которые необходимо передать функции, в отображаемое поле тела. Как показано в примере ниже:
Задание cron на изображении выше будет запускаться в 9 утра каждый день, отправляя POST
-запрос к образцу конечной точки облачной функции.
Более реалистичным вариантом использования задания cron является отправка запланированных электронных писем пользователям с заданным интервалом с использованием внешней почтовой службы, такой как Mailgun. Чтобы увидеть это в действии, мы создадим новую облачную функцию, которая отправляет электронное письмо в формате HTML на указанный адрес электронной почты, используя пакет JavaScript nodemailer для подключения к Mailgun:
# index.js require("dotenv").config(); const nodemailer = require("nodemailer"); exports.Emailer = (req, res) => { let sender = process.env.SENDER; const { reciever, type } = req.body var transport = nodemailer.createTransport({ host: process.env.HOST, port: process.env.PORT, secure: false, auth: { user: process.env.SMTP_USERNAME, pass: process.env.SMTP_PASSWORD, }, }); if (!reciever) { res.status(400).send({ error: `Empty email address` }); } transport.verify(function (error, success) { if (error) { res .status(401) .send({ error: `failed to connect with stmp. check credentials` }); } }); switch (type) { case "statistics": return transport.sendMail( { from: sender, to: reciever, subject: "Your usage satistics of demo app", html: { path: "./welcome.html" }, }, (error, info) => { if (error) { res.status(401).send({ error : error }); } transport.close(); res.status(200).send({data : info}); } ); default: res.status(500).send({ error: "An available email template type has not been matched.", }); } };
Using the cloud function above we can send an email to any user's email address specified as the receiver value in the request body. It performs the sending of emails through the following steps:
- It creates an SMTP transport for sending messages by passing the
host
,user
andpass
which stands for password, all displayed on the user's Mailgun dashboard when a new account is created. - Next, it verifies if the SMTP transport has the credentials needed in order to establish a connection. If there's an error in establishing the connection, it ends the function's invocation and sends back a
401 unauthenticated
status code. - Next, it calls the
sendMail
method to send the email containing the HTML file as the email's body to the receiver's email address specified in theto
field.
Note : We use a switch statement in the cloud function above to make it more reusable for sending several emails for different recipients. This way we can send different emails based on the type
field included in the request body when calling this cloud function.
Now that there is a function that can send an email to a user; we are left with creating the cron job to invoke this cloud function. This time, the cron jobs are created dynamically each time a new user is created using the official Google cloud client library for the Cloud Scheduler from the initial firestoreFunction
.
We expand the CREATE-USER
case to create the job which sends the email to the created user at a one-day interval.
require("dotenv").config();cloc const { Firestore } = require("@google-cloud/firestore"); const scheduler = require("@google-cloud/scheduler") const cors = require("cors")({ origin: true }); const EMAILER = proccess.env.EMAILER_ENDPOINT const parent = ScheduleClient.locationPath( process.env.PROJECT_ID, process.env.LOCATION_ID ); exports.firestoreFunction = function (req, res) { return cors(req, res, () => { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); const client = new Scheduler.CloudSchedulerClient() if (!type) { res.status(422).send({ error : "An action type was not specified"}); } switch (type) { case "CREATE-USER":
const job = { httpTarget: { uri: process.env.EMAIL_FUNCTION_ENDPOINT, httpMethod: "POST", body: { email: email, }, }, schedule: "*/30 */6 */5 10 4", timezone: "Africa/Lagos", }
if (!email || !password) { res.status(422).send("email and password fields missing"); } return bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(password, salt, (err, hash) => { document .add({ email: email, password: hash, }) .then((response) => {
client.createJob({ parent : parent, job : job }).then(() => res.status(200).send(response)) .catch(e => console.log(`unable to create job : ${e}`) )
}) .catch((e) => res.status(501).send(`error inserting data : ${e}`) ); }); }); default: res.status(422).send(`${type} is not a valid function action`) } }); };
From the snippet above, we can see the following:
- A connection to the Cloud Scheduler from the Scheduler constructor using the Application Default Credentials (ADC) is made.
- We create an object consisting of the following details which make up the cron job to be created:
-
uri
The endpoint of our email cloud function in which a request would be made to. -
body
This is the data containing the email address of the user to be included when the request is made. -
schedule
The unix cron format representing the time when this cron job is to be performed.
-
- After the promise from inserting the user's data document is resolved, we create the cron job by calling the
createJob
method and passing in the job object and the parent. - The function's execution is ended with a
200
status code after the promise from thecreateJob
operation has been resolved.
After the job is created, we'll see it listed on the scheduler page.
From the image above we can see the time scheduled for this job to be executed. We can decide to manually run this job or wait for it to be executed at the scheduled time.
Заключение
Within this article, we have had a good look into serverless applications and the benefits of using them. We also had an extensive look at how developers can manage their serverless applications on the Google Cloud using Cloud Functions so you now know how the Google Cloud is supporting the use of serverless applications.
Within the next years to come, we will certainly see a large number of developers adapt to the use of serverless applications when building applications. If you are using cloud functions in a production environment, it is recommended that you read this article from a Google Cloud advocate on “6 Strategies For Scaling Your Serverless Applications”.
The source code of the created cloud functions are available within this Github repository and also the used front-end application within this Github repository. The front-end application has been deployed using Netlify and can be tested live here.
использованная литература
- Облако Google
- Облачные функции
- Cloud Source Repositories
- Cloud Scheduler overview
- Облако Firestore
- “6 Strategies For Scaling Your Serverless Applications,” Preston Holmes