Fermer

novembre 6, 2020

Création d'applications frontales sans serveur à l'aide de Google Cloud Platform


À propos de l'auteur

Nwani Victory travaille en tant qu'ingénieur frontend chez Liferithms.inc de Lagos, Nigeria. Après les heures de bureau, il se double d'un ingénieur cloud à la recherche de moyens de faire du cloud…
En savoir plus sur
Nwani

L'utilisation d'applications sans serveur par les développeurs pour gérer la logique métier de leurs applications est en forte augmentation, mais comment Google Cloud – un fournisseur de services majeur du cloud public – permet-il aux développeurs de gérer des applications sans serveur ? Dans cet article, vous apprendrez ce que sont les applications sans serveur, comment elles sont utilisées sur Google Cloud, ainsi que les scénarios dans lesquels elles peuvent être utilisées dans une application frontale.

Récemment, le paradigme de développement des applications a commencé à passer du déploiement manuel, de la mise à l'échelle et de la mise à jour des ressources utilisées dans une application à la dépendance à des fournisseurs de services cloud tiers pour faire la plupart de la gestion de ces ressources.

En tant que développeur ou organisation qui souhaite créer un marché -fit application dans les plus brefs délais, votre objectif principal peut être de fournir votre service d'application principal à vos utilisateurs, tandis que vous passez moins de temps à configurer, déployer et tester votre application. Si tel est votre cas d'utilisation, la gestion de la logique métier de votre application sans serveur est la meilleure option. Mais comment?

Cet article est utile aux ingénieurs frontaux qui souhaitent créer certaines fonctionnalités au sein de leur application ou aux ingénieurs back-end qui souhaitent extraire et gérer une certaine fonctionnalité d'un service back-end existant à l'aide d'une application sans serveur déployée sur la plate-forme Google Cloud.

Remarque : Pour bénéficier de ce qui sera traité ici, vous devez avoir une expérience de travail avec React. Aucune expérience préalable des applications sans serveur n'est requise.

Avant de commencer, voyons ce que sont réellement les applications sans serveur et comment l'architecture sans serveur peut être utilisée lors de la construction d'une application dans le contexte d'un frontend

Applications sans serveur

Les applications sans serveur sont des applications décomposées en de minuscules fonctions événementielles réutilisables, hébergées et gérées par des fournisseurs de services cloud tiers dans le cloud public au nom de l'auteur de l'application. Celles-ci sont déclenchées par certains événements et sont exécutées à la demande. Bien que le suffixe « less » associé au mot serverless indique l'absence de serveur, ce n'est pas à 100% le cas. Ces applications fonctionnent toujours sur des serveurs et d'autres ressources matérielles, mais dans ce cas, ces ressources ne sont pas fournies par le développeur mais plutôt par un fournisseur de services cloud tiers. Donc, ils sont serveur- moins pour l'auteur de l'application mais fonctionnent toujours sur des serveurs et sont accessibles sur Internet public.

Un exemple d'utilisation d'une application sans serveur serait d'envoyer des e-mails à des utilisateurs potentiels qui visitent votre page de destination et abonnez-vous pour recevoir des e-mails de lancement de produit. À ce stade, vous n'avez probablement pas de service back-end en cours d'exécution et ne voudriez pas sacrifier le temps et les ressources nécessaires pour en créer, déployer et gérer un, tout cela parce que vous devez envoyer des e-mails. Ici, vous pouvez écrire un fichier unique qui utilise un client de messagerie et le déployer sur n'importe quel fournisseur de cloud prenant en charge les applications sans serveur et les laisser gérer cette application en votre nom pendant que vous connectez cette application sans serveur à votre page de destination.

Bien qu'il existe un Une multitude de raisons pour lesquelles vous pourriez envisager de tirer parti des applications sans serveur ou des fonctions en tant que service (FAAS) comme on les appelle, pour votre application frontale, voici quelques raisons très notables à prendre en compte:

  • Application auto scaling
    Serverless applications sont mis à l'échelle horizontalement et cette « mise à l'échelle » est automatiquement effectuée par le fournisseur Cloud en fonction du nombre d'appels, de sorte que le développeur n'a pas à ajouter ou supprimer manuellement des ressources lorsque l'application est soumise à une forte charge.
  • Rentabilité
    Étant donné que les applications sans serveur sont basées sur les événements, elles ne s'exécutent que lorsque cela est nécessaire, ce qui se répercute sur les frais, car elles sont facturées en fonction de
  • Flexibilité
    Les applications sans serveur sont conçues pour être hautement réutilisables, ce qui signifie qu'elles ne sont pas liées à un seul projet ou application. Une fonctionnalité particulière peut être extraite dans une application sans serveur, déployée et utilisée dans plusieurs projets ou applications. Les applications sans serveur peuvent également être écrites dans la langue préférée de l'auteur de l'application, bien que certains fournisseurs de cloud ne prennent en charge qu'un plus petit nombre de langues.

Lors de l'utilisation d'applications sans serveur, chaque développeur dispose d'un vaste éventail de fournisseurs de cloud dans le cloud public utiliser. Dans le contexte de cet article, nous nous concentrerons sur les applications sans serveur sur la Google Cloud Platform – comment elles sont créées, gérées, déployées et comment elles s'intègrent également avec d'autres produits sur le ] Google Cloud . Pour ce faire, nous ajouterons de nouvelles fonctionnalités à cette application existante React tout en travaillant à travers le processus de:

  • Organisation des flux de travail des applications à l'aide de Google Cloud.
  • Stockage et récupération données des utilisateurs sur le cloud.
  • Création et gestion de tâches Cron sur Google Cloud.
  • Déploiement de Cloud Functions sur Google Cloud.

Remarque : Les applications sans serveur ne sont pas liées à React uniquement, tant que votre infrastructure frontale ou bibliothèque préférée peut effectuer une requête HTTP elle peut utiliser une application sans serveur.

Fonctions Google Cloud

Google Cloud permet aux développeurs de créer applications sans serveur utilisant Cloud Functions et les exécute à l'aide du Functions Framework . Comme on les appelle, les fonctions Cloud sont des fonctions événementielles réutilisables déployées sur Google Cloud pour écouter le déclencheur spécifique sur les six déclencheurs d'événements disponibles puis effectuer l'opération pour laquelle il a été écrit

Les fonctions cloud de courte durée ( avec un délai d'exécution par défaut de 60 secondes et un maximum de 9 minutes ) peuvent être écrites en utilisant JavaScript Python Golang et Java et exécutés en utilisant leur runtime. En JavaScript, ils peuvent être exécutés en utilisant uniquement certaines versions disponibles du runtime Node et sont écrits sous la forme de modules CommonJS en utilisant JavaScript brut tel quel exportée en tant que fonction principale à exécuter sur Google Cloud.

Un exemple de fonction cloud est celui ci-dessous qui est un passe-partout vide pour la fonction permettant de gérer les données d'un utilisateur.

 // index.js

exports.firestoreFunction = function (req, res) {
  return res.status (200) .send ({data: `Hello $ {req.query.name}`});
} 

Ci-dessus, nous avons un module qui exporte une fonction. Lorsqu'il est exécuté, il reçoit les arguments de requête et de réponse similaires à une route HTTP .

Remarque : Une fonction cloud correspond à tous les protocoles HTTP lorsqu'une requête est fait. Cela vaut la peine d'être noté lors de l'attente de données dans l'argument de requête, car les données jointes lors d'une requête pour exécuter une fonction cloud seraient présentes dans le corps de la requête pour les requêtes POST tandis que dans le corps de la requête pour GET requêtes.

Les fonctions cloud peuvent être exécutées localement pendant le développement en installant le package @ google-cloud / functions-framework dans le même dossier où la fonction écrite est placée ou en effectuant une installation globale sur utilisez-le pour plusieurs fonctions en exécutant npm i -g @ google-cloud / functions-framework depuis votre ligne de commande. Une fois installé, il doit être ajouté au script package.json avec le nom du module exporté similaire à celui ci-dessous:


 "scripts": {
     "start": "functions-framework --target = firestoreFunction --port = 8000",
  } 

Ci-dessus, nous avons une seule commande dans nos scripts dans le fichier package.json qui exécute le framework de fonctions et spécifie également la fonction firestoreFunction comme fonction cible à exécuter localement sur le port 8000 .

Nous pouvons tester le point de terminaison de cette fonction en faisant une requête GET au port 8000 sur localhost en utilisant curl . Coller la commande ci-dessous dans un terminal le fera et retournera une réponse.

 curl http: // localhost: 8000? Name = "Smashing Magazine Author" 

La requête ci-dessus lorsqu'elle est exécutée fait une requête avec un GET HTTP et répond avec un code d'état 200 et une donnée d'objet contenant le nom ajouté dans la requête.

Déploiement d'une fonction cloud

Hors des méthodes de déploiement, un moyen rapide de déployer une fonction cloud à partir d'une machine locale est d'utiliser le cloud Sdk après l'installer . L'exécution de la commande ci-dessous à partir du terminal après authentification du gcloud sdk avec votre projet sur Google Cloud, déploierait une fonction créée localement sur le service Cloud Function.

 gcloud functions deploy "demo-function" --runtime nodejs10 --trigger-http --entry-point = demo --timeout = 60 --set-env-vars = [name="Developer"] --allow-unauthenticated 

Utilisation des indicateurs expliqués ci-dessous , la commande ci-dessus déploie une fonction déclenchée HTTP sur le cloud Google avec le nom « demo-function ».

  • NAME
    Il s'agit du nom donné à une fonction cloud lors de son déploiement et est obligatoire .
  • region
    C'est la région où la fonction cloud doit être déployée. Par défaut, il est déployé sur us-central1 .
  • trigger-http
    Ceci sélectionne HTTP comme type de déclenchement de la fonction.
  • allow-unauthenticated
    Cela permet à la fonction d'être appelée en dehors de Google Cloud via Internet en utilisant son point de terminaison généré sans vérifier si l'appelant est authentifié.
  • source
    Chemin local du terminal vers le fichier contenant la fonction à être déployé.
  • point d'entrée
    Il s'agit du module exporté spécifique à déployer à partir du fichier où les fonctions ont été écrites.
  • runtime
    Il s'agit du langage d'exécution à utiliser pour la fonction parmi cette liste d'exécution acceptée.
  • timeout
    C'est le temps maximum qu'une fonction peut exécuter avant l'expiration du délai. Elle est de 60 secondes par défaut et peut être définie sur un maximum de 9 minutes.

Remarque : Si une fonction autorise les requêtes non authentifiées, toute personne disposant du point de terminaison de votre fonction peut également effectuer des requêtes sans que vous ne l’autorisiez. Pour atténuer cela, nous pouvons nous assurer que le point final reste privé en l'utilisant via variables d'environnement ou en demandant en-têtes d'autorisation à chaque demande.

Maintenant que notre démo -fonction a été déployée et nous avons le point final, nous pouvons tester cette fonction comme si elle était utilisée dans une application du monde réel en utilisant une installation globale de autocannon . L'exécution de autocannon -d = 5 -c = 300 CLOUD_FUNCTION_URL à partir du terminal ouvert générerait 300 requêtes simultanées à la fonction cloud sur une durée de 5 secondes. Cela suffit amplement pour démarrer la fonction cloud et générer également des métriques que nous pouvons explorer sur le tableau de bord de la fonction.

Remarque: Le point de terminaison d'une fonction serait imprimé dans le terminal après le déploiement. Si ce n'est pas le cas, exécutez gcloud function describe FUNCTION_NAME depuis le terminal pour obtenir les détails de la fonction déployée, y compris le point de terminaison.

En utilisant l'onglet metrics du tableau de bord, nous pouvons voir une représentation visuelle de la dernière requête consistant en combien d'appels ont été effectués, combien de temps ils ont duré, l'empreinte mémoire de la fonction et combien d'instances ont été lancées pour gérer les requêtes effectuées.

 Tableau de bord d'une fonction montrant un graphique des métriques de toutes les demandes récentes effectuées.
Tableau de bord de la fonction Cloud affichant toutes les demandes effectuées. ( Grand aperçu )

Un examen plus approfondi du graphique Active Instances dans l'image ci-dessus montre la capacité de mise à l'échelle horizontale des fonctions Cloud, car nous pouvons voir que 209 instances ont été lancées en quelques secondes pour gérer les requêtes effectuées à l'aide du canon automatique .

Journaux des fonctions du cloud

Chaque fonction déployée sur le cloud Google a un journal et chaque fois que cette fonction est exécutée, une nouvelle entrée dans ce journal est effectuée. Dans l'onglet Journal du tableau de bord de la fonction, nous pouvons voir une liste de toutes les entrées de journaux d'une fonction cloud.

Voici les entrées de journal de notre démo-fonction déployée créé à la suite des requêtes que nous avons effectuées à l'aide du canon automatique .

 Le journal de la fonction cloud affichant les journaux des heures d'exécution de la fonction.
Onglet du journal de la fonction cloud affichant tous les journaux d'exécution. ( Grand aperçu )

Chacune des entrées de journal ci-dessus montre exactement quand une fonction a été exécutée, combien de temps l'exécution a pris et avec quel code d'état elle s'est terminée. S'il y a des erreurs résultant d'une fonction, les détails de l'erreur, y compris la ligne à laquelle elle s'est produite, seront affichés dans les journaux ici.

L'explorateur de journaux sur Google Cloud peut être utilisé pour obtenir des informations plus complètes. détails sur les journaux d'une fonction cloud.

Fonctions cloud avec applications frontales

Les fonctions cloud sont très utiles et puissantes pour les ingénieurs frontend. Un ingénieur frontend sans connaissance de la gestion des applications back-end peut extraire une fonctionnalité dans une fonction cloud, la déployer sur Google Cloud et l'utiliser dans une application frontend en envoyant des requêtes HTTP à la fonction cloud via son point de terminaison.

Pour montrer comment les fonctions cloud peuvent être utilisées dans une application frontend, nous ajouterions plus de fonctionnalités à cette application React. L'application dispose déjà d'un routage de base entre l'authentification et la configuration des pages d'accueil. Nous l'étendrons pour utiliser l ' API React Context pour gérer l'état de notre application, car l'utilisation des fonctions cloud créées se ferait dans les réducteurs d'application.

Pour commencer, nous créons le contexte de notre application à l'aide de l'API createContext et également créer un réducteur pour gérer les actions dans notre application.

 // state / index.js
importer {createContext} de "react";

export const UserReducer = (action, état) => { commutateur (action.type) { cas "CREATE-USER": Pause; cas "UPLOAD-USER-IMAGE": Pause; cas «FETCH-DATA»: Pause cas «LOGOUT»: Pause; défaut: console.log ( $ {action.type} n'est pas reconnu ) } };

export const userState = { utilisateur: nul, isLoggedIn: false };

export const UserContext = createContext (userState);

Ci-dessus, nous avons commencé par créer une fonction UserReducer qui contient une instruction switch, lui permettant d'effectuer une opération basée sur le type d'action distribuée. L'instruction switch a quatre cas et ce sont les actions que nous allons gérer. Pour l'instant, ils ne font encore rien, mais lorsque nous commencerons à intégrer nos fonctions cloud, nous implémenterons de manière incrémentielle les actions à effectuer.

Nous avons également créé et exporté le contexte de notre application à l'aide de l'API React createContext et nous l'avons donné une valeur par défaut de l'objet userState qui contient une valeur utilisateur actuellement qui serait mise à jour de null aux données de l'utilisateur après authentification et également une valeur booléenne isLoggedIn pour savoir si l'utilisateur est connecté

Maintenant, nous pouvons continuer à consommer notre contexte, mais avant de faire cela, nous devons envelopper l'ensemble de notre arborescence d'application avec le fournisseur attaché au UserContext pour que les composants enfants puissent pour souscrire au changement de valeur de notre contexte.

 // index.js
importer React depuis "react";
importer ReactDOM depuis "react-dom";
import "./index.css";
importer l'application depuis "./app";
import {UserContext, userState} de "./state/";

ReactDOM.render (
  
    
      
    
  ,
  document.getElementById ("racine")
);

serviceWorker.unregister ();

Nous enveloppons notre application enter avec le fournisseur UserContext au niveau du composant racine et passons notre valeur par défaut userState précédemment créée dans la valeur prop.

Maintenant que nous avons notre application état entièrement configuré, nous pouvons passer à la création du modèle de données de l'utilisateur à l'aide de Google Cloud Firestore via une fonction cloud.

Gestion des données d'application

Les données d'un utilisateur dans cette application se composent d'un identifiant unique, d'un e-mail, d'un mot de passe et l'URL d'une image. En utilisant une fonction cloud, ces données seront stockées sur le cloud à l'aide du service Cloud Firestore qui est proposé sur la plate-forme Google Cloud.

Le Google Cloud Firestore une base de données NoSQL flexible a été créé à partir de Firebase Realtime Database avec de nouvelles fonctionnalités améliorées qui permettent des requêtes plus riches et plus rapides ainsi que la prise en charge des données hors ligne. Les données du service Firestore sont organisées en collections.

Le Firestore est accessible visuellement via la console Google Cloud. Pour le lancer, ouvrez le volet de navigation de gauche et faites défiler jusqu'à la section Base de données et cliquez sur Firestore. Cela afficherait la liste des collections pour les utilisateurs avec des données existantes ou inviterait l'utilisateur à créer une nouvelle collection lorsqu'il n'y a pas de collection existante. Nous créerions une collection users à utiliser par notre application.

Semblable à d'autres services sur Google Cloud Platform, Cloud Firestore dispose également d'une bibliothèque cliente JavaScript conçue pour être utilisée dans un environnement de nœud ( une erreur serait générée si elle était utilisée dans le navigateur ). Pour improviser, nous utilisons Cloud Firestore dans une fonction cloud en utilisant le package @ google-cloud / firestore .

Utilisation de Cloud Firestore avec une fonction Cloud

Pour commencer, nous renommerions le première fonction que nous avons créée à partir de demo-cloud-function à firestoreFunction puis étendez-la pour vous connecter à notre collection d'utilisateurs sur le Firestore et également enregistrer et connecter les utilisateurs.

 require (" dotenv "). config ();
const {Firestore} = require ("@ google-cloud / firestore");
const {SecretManagerServiceClient} = require ("@ google-cloud / secret-manager");

client const = nouveau SecretManagerServiceClient ();
        
exports.firestoreFunction = function (req, res) {
    revenir {
        const {email, mot de passe, type} = req.body;
        const firestore = nouveau Firestore ();
        const document = firestore.collection ("utilisateurs");
        console.log (document) // imprime les détails de la collection dans les journaux de fonction
        if (! type) {
            res.status (422) .send ("Un type d'action n'a pas été spécifié");
        }

        commutateur (type) {
            case "CREATE-USER":
                Pause
            case "LOGIN-USER":
                Pause;
            défaut:
                res.status (422) .send (`$ {type} n'est pas une action de fonction valide`)
        }
};

Pour gérer plus d'opérations impliquant le fire-store, nous avons ajouté une instruction switch avec deux cas pour gérer les besoins d'authentification de notre application. Notre instruction switch évalue une expression de type que nous ajoutons au corps de la requête lorsque nous faisons une requête à cette fonction depuis notre application et chaque fois que ces données de type ne sont pas présentes dans notre corps de requête, le La demande est identifiée comme une demande incorrecte et un code d'état 400 à côté d'un message indiquant le type manquant est envoyé comme réponse.

Nous établissons une connexion avec le Firestore à l'aide du Bibliothèque d'informations d'identification par défaut de l'application (ADC) dans la bibliothèque cliente Cloud Firestore. Sur la ligne suivante, nous appelons la méthode de collection dans une autre variable et passons le nom de notre collection. Nous l'utiliserons pour effectuer d'autres opérations sur la collection des documents contenus.

Remarque: Les bibliothèques clientes pour les services sur Google Cloud se connectent à leur service respectif à l'aide d'une clé de compte de service créée transmise lors de l'initialisation du constructeur. Lorsque la clé de compte de service n'est pas présente, il utilise par défaut les Identifiants par défaut de l'application qui à leur tour se connectent à l'aide des rôles IAM affectés à la fonction cloud.

Après avoir modifié le code source d'une fonction qui a été déployée localement à l'aide de gcloud sdk, nous pouvons réexécuter la commande précédente depuis un terminal pour mettre à jour et redéployer la fonction cloud.

Maintenant qu'une connexion a été établie, nous pouvons implémenter le CREATE -USER pour créer un nouvel utilisateur en utilisant les données du corps de la requête, puis passer à LOGIN-USER qui trouve un utilisateur existant et renvoie un cookie.


 require ("dotenv ") .config ();
const {Firestore} = require ("@ google-cloud / firestore");
const path = require ("path");
const {v4: uuid} = require ("uuid")
const cors = require ("cors") ({origin: true});

client const = nouveau SecretManagerServiceClient ();

exports.firestoreFunction = function (req, res) {
    renvoyer cors (req, res, () => {
        const {email, mot de passe, type} = req.body;
        const firestore = nouveau Firestore ();
        const document = firestore.collection ("utilisateurs");
        if (! type) {
            res.status (422) .send ("Un type d'action n'a pas été spécifié");
        }

        commutateur (type) {
            case "CREATE-USER":
              if (! email ||! mot de passe) {
                res.status (422) .send ("champs email et mot de passe manquants");
              }
            
            id const = uuid ()
            retourne bcrypt.genSalt (10, (err, sel) => {
              bcrypt.hash (mot de passe, sel, (err, hachage) => {
                document.doc (id)
                  .ensemble({
                    J'ai fait
                    email: email,
                    mot de passe: hash,
                    img_uri: null
                   })
                  .then ((réponse) => res.status (200) .send (réponse))
                  .catch ((e) =>
                      res.status (501) .send ({erreur: e})
                    );
                  });
                });

           cas "LOGIN":
              Pause;
          défaut:
            res.status (400) .send (`$ {type} n'est pas une action de fonction valide`)
        }
    });
};

Nous avons généré un UUID en utilisant le package uuid à utiliser comme identifiant du document sur le point d'être enregistré en le passant dans la méthode set on le document et également l'identifiant de l'utilisateur. Par défaut, un ID aléatoire est généré sur chaque document inséré, mais dans ce cas, nous mettrons à jour le document lors de la gestion du téléchargement d'image et l'UUID est ce qui serait utilisé pour obtenir un document particulier à mettre à jour. Plutôt que de stocker le mot de passe de l'utilisateur en texte brut, nous le salons d'abord à l'aide de bcryptjs puis stockons le hachage du résultat comme mot de passe de l'utilisateur.

Intégration de la fonction cloud firestoreFunction dans l'application, nous l'utilisons à partir du cas CREATE_USER dans le réducteur utilisateur.

Après avoir cliqué sur le bouton Créer un compte une action est envoyée aux réducteurs avec un CREATE_USER tapez pour faire une requête POST contenant l'adresse e-mail et le mot de passe saisis au point de terminaison de la fonction firestoreFunction .

 import {createContext} from "react";
importer {naviguer} depuis "@ reach / router";
importer Axios depuis "axios";

export const userState = {
  utilisateur: nul,
  isLoggedIn: faux,
};

export const UserReducer = (état, action) => {
  commutateur (action.type) {
    cas "CREATE_USER":
      const FIRESTORE_FUNCTION = process.env.REACT_APP_FIRESTORE_FUNCTION;
      const {userEmail, userPassword} = action;

      const data = {
        tapez: "CREATE-USER",
        email: userEmail,
        mot de passe: userPassword,
      };

      Axios.post (`$ {FIRESTORE_FUNCTION}`, données)
        .then ((res) => {
          naviguer ("/ home");
          return {... état, isLoggedIn: true};
        })
        .catch ((e) => console.log (`impossible de créer l'utilisateur. erreur: $ {e}`));
      Pause;
    case "LOGIN-USER":
      Pause;
    case "UPLOAD-USER-IMAGE":
      Pause;
    cas "FETCH-DATA":
      Pause
    cas "LOGOUT":
      naviguer ("/ login");
      return {... état, isLoggedIn: false};
    défaut:
      Pause;
  }
};

export const UserContext = createContext (userState);

Ci-dessus, nous avons utilisé Axios pour faire la demande à la firestoreFunction et après que cette demande a été résolue, nous avons défini l'état initial de l'utilisateur de null aux données renvoyées par le

Ensuite, nous passons à implémenter une fonctionnalité de connexion dans notre fonction firestoreFunction pour permettre à un utilisateur existant de se connecter à son compte en utilisant leurs identifiants enregistrés.


 require ("dotenv"). config ();
const {Firestore} = require ("@ google-cloud / firestore");
const path = require ("path");
const cors = require ("cors") ({origin: true});

client const = nouveau SecretManagerServiceClient ()
exports.firestoreFunction = function (req, res) {
    renvoyer cors (req, res, () => {
        const {email, mot de passe, type} = req.body;
        const firestore = nouveau Firestore ();
        const document = firestore.collection ("utilisateurs");
        if (! type) {
            res.status (422) .send ("Un type d'action n'a pas été spécifié");
        }

        commutateur (type) {
            cas "CRÉER":
                // ... CRÉER - LOGIQUE UTILISATEUR
                Pause
            cas "LOGIN":
                Pause;
            défaut:
                res.status (500) .send ({error: `$ {type} n'est pas une action valide`})
        }
    });
};

À ce stade, un nouvel utilisateur peut créer un compte avec succès et être dirigé vers la page d'accueil. Ce processus montre comment nous utilisons Cloud Firestore pour effectuer la sauvegarde et la mutation de base des données dans une application sans serveur.

Gestion du stockage de fichiers

Le stockage et la récupération des fichiers d'un utilisateur dans une application sont la plupart du temps indispensables. fonctionnalité dans une application. Dans une application connectée à un backend node.js, Multer est souvent utilisé comme middleware pour gérer le multipart / form-data dans lequel un fichier téléchargé entre. Mais en l'absence de le backend node.js, nous pourrions utiliser un service de stockage de fichiers en ligne tel que Google Cloud Storage pour stocker des fichiers.

Le Google Cloud Storage est un service de stockage de fichiers disponible dans le monde entier utilisé pour stocker toute quantité de données en tant qu'objets pour les applications dans des buckets. Il est suffisamment flexible pour gérer le stockage des ressources statiques pour les applications de petite et de grande taille.

Pour utiliser le service Cloud Storage dans une application, nous pouvons utiliser les points de terminaison de l'API de stockage ou en utilisant la bibliothèque cliente officielle de stockage de nœud. Cependant, la bibliothèque cliente Node Storage ne fonctionne pas dans une fenêtre de navigateur, nous pourrions donc utiliser une fonction cloud là où nous utiliserions la bibliothèque.

Un exemple de cela est la fonction cloud ci-dessous qui se connecte et télécharge un fichier sur un Cloud Bucket créé.

 const cors = require ("cors") ({origin: true});
const {Storage} = require ("@ google-cloud / storage");
const StorageClient = nouveau stockage ();

exports.Uploader = (req, res) => {
    const {fichier} = req.body;
    StorageClient.bucket ("TEST_BUCKET")
      .file (fichier.nom)
      .then ((réponse) => {
         console.log (réponse);
        res.status (200) .send (réponse)
       })
      .catch ((e) => res.status (422) .send ({erreur: e}));
  });
};

À partir de la fonction cloud ci-dessus, nous effectuons les deux opérations principales suivantes:

  • Tout d'abord, nous créons une connexion au Cloud Storage dans le Storage constructor et il utilise le ] Application Default Credentials (ADC) sur Google Cloud pour s'authentifier auprès du Cloud Storage.

  • Ensuite, nous téléchargeons le fichier inclus dans le corps de la requête dans notre TEST_BUCKET ] en appelant la méthode .file et en passant le nom du fichier. Comme il s'agit d'une opération asynchrone, nous utilisons une promesse pour savoir quand cette action a été résolue et nous renvoyons une réponse 200 mettant ainsi fin au cycle de vie de l'invocation.

Maintenant, nous pouvons développer la fonction Cloud Uploader ci-dessus pour gérer le téléchargement de l'image de profil d'un utilisateur. La fonction cloud recevra l'image de profil d'un utilisateur, la stockera dans le compartiment cloud de notre application, puis mettra à jour les données de l'utilisateur img_uri dans la collection de nos utilisateurs dans le service Firestore.

 require ("dotenv") .config ();
const {Firestore} = require ("@ google-cloud / firestore");
const cors = require ("cors") ({origin: true});
const {Storage} = require ("@ google-cloud / storage");

const StorageClient = nouveau stockage ();
const BucketName = process.env.STORAGE_BUCKET

exports.Uploader = (req, res) => {
  retourne Cors (req, res, () => {
    const {fichier, userId} = req.body;
    const firestore = nouveau Firestore ();
    const document = firestore.collection ("utilisateurs");

    StorageClient.bucket (BucketName)
      .file (fichier.nom)
      .on ("terminer", () => {
        StorageClient.bucket (BucketName)
          .file (fichier.nom)
          .rendre publique()
          .then (() => {
              const img_uri = `https://storage.googleapis.com/$ {Bucket} / $ {file.path}`;
                document
                 .doc (userId)
                 .mise à jour({
                      img_uri,
                  })
                  .then ((updateResult) => res.status (200) .send (updateResult))
                  .catch ((e) => res.status (500) .send (e));
                  })
          .catch ((e) => console.log (e));
      });
  });
}; 

Nous avons maintenant développé la fonction Upload ci-dessus pour effectuer les opérations supplémentaires suivantes:

  • Premièrement, il établit une nouvelle connexion au service Firestore pour obtenir notre collection users en initialisant le constructeur Firestore et il utilise les Application Default Credentials (ADC) pour s'authentifier auprès du Cloud Storage.

  • Après avoir téléchargé le fichier ajouté dans le corps de la requête, nous le rendons public dans l'ordre pour être accessible via une URL publique en appelant la méthode makePublic sur le fichier téléchargé. Selon le contrôle d'accès par défaut de Cloud Storage, sans rendre un fichier public, il est impossible d'accéder à un fichier sur Internet et de pouvoir le faire lorsque l'application se charge.

Remarque: Rendre un fichier public signifie que toute personne utilisant votre application peut copier le lien du fichier et avoir un accès illimité au fichier. One way to prevent this is by using a Signed URL to grant temporary access to a file within your bucket instead of making it fully public.

  • Next, we update the user’s existing data to include the URL of the file uploaded. We find the particular user’s data using Firestore’s WHERE query and we use the userId included in the request body, then we set the img_uri field to contain the URL of the newly updated image.

The Upload cloud function above can be used within any application having registered users within the Firestore service. All that is needed to make a POST request to the endpoint, putting the user’s IS and an image in the request body.

An example of this within the application is the UPLOAD-FILE case which makes a POST request to the function and puts the image link returned from the request in the application state.

# 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`)
  }
}

From the switch case above, we make a POST request using Axios to the UPLOAD_FUNCTION passing in the added file to be included in the request body and we also added an image Content-Type in the request header.

After a successful upload, the response returned from the cloud function would contain the user’s data document which has been updated to contain a valid url of the image uploaded to the google cloud storage. We can then update the user’s state to contain the new data and this would also update the user’s profile image src element in the profile component.

A user’s profile page which with an update profile image
A user’s profile page which has just been updated to show the newly updated profile image. (Large preview)

Handling Cron Jobs

Repetitive automated tasks such as sending emails to users or performing an internal action at a specific time are most times an included feature of applications. In a regular node.js application, such tasks could be handled as cron jobs using node-cron or node-schedule. When building serverless applications using the Google Cloud Platform, the Cloud Scheduler is also designed to perform a cron operation.

Note: Although the Cloud Scheduler works similar to the Unix cron utility in creating jobs that are executed in the future, it is important to note that the Cloud Scheduler does not execute a command as the cron utility does. Rather it performs an operation using a specified target.

As the name implies, the Cloud Scheduler allows users to schedule an operation to be performed at a future time. Each operation is called a job and jobs can be visually created, updated, and even destroyed from the Scheduler section of the Cloud Console. Asides from a name and description field, jobs on the Cloud Scheduler consist of the following:

  • Frequency: This is used to schedule the execution of the Cron job. Schedules are specified using the unix-cron format which is originally used when creating background jobs on the cron table in a Linux environment. The unix-cron format consists of a string with five values each representing a time point. Below we can see each of the five strings and the values they represent.
   - - - - - - - - - - - - - - - -   minute ( - 59 )
  | - -  - - - - - -  - - - -  -  hour ( 0 - 23 )
  | | - - - - - - -  - - - - -  day of month ( 1 - 31 )
  | | | - -  - - - -  - - -  month ( 1 - 12 )
  | | | | - - -  - - --  day of week ( 0 - 6 )
  | | | | |
  | | | | |
  | | | | |
  | | | | |
  | | | | |
  *   *   *    *    * 

The Crontab generator tool comes in handy when trying to generate a frequency-time value for a job. If you are finding it difficult to put the time values together, the Crontab generator has a visual drop-down where you can select the values that make up a schedule and you copy the generated value and use as the frequency.

  • Timezone: The timezone from where the cron job is executed. Due to the time difference between time-zones, cron jobs executed with different specified time-zones would have different execution times.

  • Target: This is what is used in the execution of the specified Job. A target could be an HTTP type where the job makes a request at the specified time to URL or a Pub/Sub topic which the job can publish messages to or pull messages from and lastly an App Engine Application.

The Cloud Scheduler combines perfectly well with HTTP triggered Cloud Functions. When a job within the Cloud Scheduler is created with its target set to HTTP, this job can be used to executed a cloud function. All that needs to be done is to specify the endpoint of the cloud function, specify the HTTP verb of the request then add whatever data needs to be passed to function in the displayed body field. As shown in the sample below:

Fields required for creating a cron job using the cloud console
Fields required for creating a cron job using the cloud console. (Large preview)

The cron job in the image above would run by 9 AM every day making a POST request to the sample endpoint of a cloud function.

A more realistic use case of a cron job is sending scheduled emails to users at a given interval using an external mailing service such as Mailgun. To see this in action, we will create a new cloud function which sends a HTML email to a specified email address using the nodemailer JavaScript package to connect to 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 hostuser and pass 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 the to 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 would be 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 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 the createJob operation has been resolved.

After the job is created, we would see it listed on the scheduler page.

List of all scheduled cron jobs including the last created job.
List of all scheduled cron jobs including the last created job. (Large preview)

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.

Conclusion

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 frontend application within this Github repository. The frontend application has been deployed using Netlify and can be tested live here.

References

Smashing Editorial(ks, ra, yk, il)




Source link