Catégories
Astuces et Design

Repenser Twitter comme une application sans serveur

Dans un article précédent, nous avons montré comment construire une API GraphQL avec FaunaDB. Nous avons également écrit une série d'articles (1, 2, 3, 4) expliquant comment les bases de données traditionnelles conçues pour une évolutivité mondiale doivent adopter une cohérence éventuelle (par rapport à forte) et / ou faire des compromis sur les relations et les possibilités d'indexation. FaunaDB est différent car il ne fait pas ces compromis. Il est conçu pour évoluer afin de pouvoir servir en toute sécurité votre future startup, quelle que soit sa taille, sans sacrifier les relations et les données cohérentes.

Dans cet article, nous sommes très heureux de commencer à rassembler tout cela dans une application du monde réel avec des données hautement dynamiques sans serveur à l'aide des crochets React, FaunaDB et Cloudinary. Nous utiliserons le langage de requête Fauna (FQL) au lieu de GraphQL et commencerons par une approche frontale uniquement qui accède directement à la base de données sans serveur FaunaDB pour le stockage, l'authentification et l'autorisation des données.

La norme d'or pour les applications par exemple qui disposent d'une technologie spécifique est une application todo, principalement parce qu'elles sont simples. Toute base de données peut servir une application très simple et briller.

Et c'est exactement pourquoi cette application sera différente! Si nous voulons vraiment montrer comment FaunaDB excelle pour les applications du monde réel, alors nous devons construire quelque chose de plus avancé.

Présentation de Fwitter

Lorsque nous avons commencé sur Twitter, les bases de données étaient mauvaises. Quand nous sommes partis, ils étaient encore mauvais

Evan Weaver

Étant donné que FaunaDB a été développé par d'anciens ingénieurs de Twitter qui ont vécu ces limitations de première main, une application de type Twitter semblait être un choix sentimental approprié. Et, puisque nous le construisons avec FaunaDB, appelons ce bébé sans serveur "Fwitter ».

Ci-dessous, une courte vidéo qui montre à quoi cela ressemble, et le code source complet est disponible sur GitHub.

Lorsque vous clonez le référentiel et commencez à fouiller, vous remarquerez peut-être une pléthore d'exemples de requêtes bien commentées non couvertes par cet article. En effet, nous utiliserons Fwitter comme exemple d'application de référence dans les prochains articles et y ajouterons des fonctionnalités supplémentaires avec le temps.

Mais, pour l'instant, voici un aperçu de ce que nous allons couvrir ici:

Nous construisons ces fonctionnalités sans avoir à configurer les opérations ou configurer des serveurs pour votre base de données. Étant donné que Cloudinary et FaunaDB sont évolutifs et distribués prêts à l'emploi, nous n'aurons jamais à nous soucier de configurer des serveurs dans plusieurs régions pour obtenir de faibles latences pour les utilisateurs d'autres pays.

Plongeons-nous!

Modélisation des données

Avant de pouvoir montrer comment FaunaDB excelle dans les relations, nous devons couvrir les types de relations dans le modèle de données de notre application. Les entités de données de FaunaDB sont stockées dans des documents, qui sont ensuite stockés dans des lignes de type collections dans des tableaux. Par exemple, les détails de chaque utilisateur seront représentés par un document utilisateur stocké dans une collection Utilisateurs. Et nous prévoyons éventuellement de prendre en charge à la fois les méthodes de connexion unique et de connexion par mot de passe pour un seul utilisateur, chacune étant représentée comme un document de compte dans une collection de comptes.

À ce stade, un utilisateur possède un compte. Par conséquent, peu importe l'entité qui stocke la référence (c'est-à-dire l'ID utilisateur). Nous aurions pu stocker l'ID utilisateur dans le compte ou le document utilisateur dans une relation un à un:

Un par un

Cependant, puisqu'un utilisateur aura éventuellement plusieurs comptes (ou méthodes d'authentification), nous aurons un modèle un-à-plusieurs.

Un à plusieurs

Dans une relation un-à-plusieurs entre les utilisateurs et les comptes, chaque compte pointe vers un seul utilisateur, il est donc judicieux de stocker la référence utilisateur sur le compte:

Nous avons également des relations plusieurs-à-plusieurs, comme les relations entre Fweets et les utilisateurs, en raison des façons complexes dont les utilisateurs interagissent entre eux via des likes, des commentaires et des refweets.

Plusieurs à plusieurs

De plus, nous utiliserons une troisième collection, Fweetstats, pour stocker des informations sur l'interaction entre un utilisateur et un Fweet.

Les données de Fweetstats nous aideront à déterminer, par exemple, s'il faut colorer ou non les icônes indiquant à l'utilisateur qu'il a déjà aimé, commenté ou refweeté un Fweet. Cela nous aide également à déterminer ce que signifie cliquer sur le cœur: différent ou similaire.

Le modèle final de l'application ressemblera à ceci:

Le modèle d'application de l'application fwitter

Les Fweets sont au centre du modèle, car ils contiennent les données les plus importantes du Fweet telles que les informations sur le message, le nombre de likes, les refweets, les commentaires et le support Cloudinary qui a été attaché. FaunaDB stocke ces données dans un format json qui ressemble à ceci:

Comme indiqué dans le modèle et dans cet exemple json, les hashtags sont stockés sous forme de liste de références. Si nous le voulions, nous pourrait ont stocké le hashtag json complet ici, et c'est la solution préférée dans les bases de données documentaires plus limitées qui manquent de relations. Cependant, cela signifierait que nos hashtags seraient dupliqués partout (comme ils le sont dans des bases de données plus limitées) et il serait plus difficile de rechercher des hashtags et / ou de récupérer Fweets pour un hashtag spécifique comme indiqué ci-dessous.

Notez qu'un Fweet ne contient pas de lien vers les commentaires, mais la collection Comments contient une référence au Fweet. En effet, un commentaire appartient à un Fweet, mais un Fweet peut avoir de nombreux commentaires, similaires à la relation un-à-plusieurs entre les utilisateurs et les comptes.

Enfin, il existe une collection FollowerStats qui enregistre essentiellement des informations sur la façon dont les utilisateurs interagissent entre eux afin de personnaliser leurs flux respectifs. Nous ne couvrirons pas cela dans cet article, mais vous pouvez expérimenter avec les requêtes dans le code source et rester à l'écoute pour un futur article sur l'indexation avancée.

J'espère que vous commencez à voir pourquoi nous avons choisi quelque chose de plus complexe qu'une application ToDo. Bien que Fwitter soit loin de la complexité de la véritable application Twitter sur laquelle elle est basée, il devient déjà évident que la mise en œuvre d'une telle application sans relations serait un grave casse-tête.

Maintenant, si vous ne l'avez pas déjà fait depuis le dépôt github, il est enfin temps de lancer notre projet localement!

Configurer le projet

Pour configurer le projet, accédez au tableau de bord FaunaDB et inscrivez-vous. Une fois dans le tableau de bord, cliquez sur Nouvelle base de données, saisissez un nom et cliquez sur sauvegarder. Vous devriez maintenant être sur la page «Présentation» de votre nouvelle base de données.

Ensuite, nous avons besoin d'une clé que nous utiliserons dans nos scripts de configuration. Cliquez sur l'onglet Sécurité dans la barre latérale gauche, puis cliquez sur le Nouvelle clé bouton.

Dans le formulaire «Nouvelle clé», la base de données actuelle doit déjà être sélectionnée. Pour «Rôle», laissez-le comme «Admin». Facultativement, ajoutez un nom de clé. Ensuite, cliquez sur sauvegarder et copiez le secret de clé affiché sur la page suivante. Il ne sera plus affiché.

Maintenant que vous avez le secret de votre base de données, clonez le référentiel git et suivez le readme. Nous avons préparé quelques scripts afin que vous n'ayez qu'à exécuter les commandes suivantes pour initialiser votre application, créer toutes les collections et remplir votre base de données. Les scripts vous donneront des instructions supplémentaires:

// install node modules
npm install
// run setup, this will create all the resources in your database
// provide the admin key when the script asks for it. 
// !!! the setup script will give you another key, this is a key
// with almost no permissions that you need to place in your .env.local as the
// script suggestions

npm run setup
npm run populate
 
// start the frontend

Après le script, votre fichier .env.local doit contenir la clé de démarrage que le script vous a fournie (pas la clé d'administration)

REACT_APP_LOCAL___BOOTSTRAP_FAUNADB_KEY=

Vous pouvez éventuellement créer un compte avec Cloudinary et ajouter votre nom de cloud et un modèle public (il existe un modèle par défaut appelé «ml_default» que vous pouvez rendre public) à l'environnement pour inclure des images et des vidéos dans les fweets.

REACT_APP_LOCAL___CLOUDINARY_CLOUDNAME=
REACT_APP_LOCAL___CLOUDINARY_TEMPLATE=

Sans ces variables, le bouton include media ne fonctionnera pas, mais le reste de l'application devrait fonctionner correctement:

Création de l'extrémité avant

Pour le frontend, nous avons utilisé Create React App pour générer une application, puis divisé l'application en pages et composants. Les pages sont des composants de niveau supérieur qui ont leurs propres URL. Les pages de connexion et d'enregistrement parlent d'elles-mêmes. Home est le flux standard de Fweets des auteurs que nous suivons; c'est la page que nous voyons lorsque nous nous connectons à notre compte. Et les pages Utilisateur et Tag affichent les Fweets pour un utilisateur ou un tag spécifique dans l'ordre chronologique inverse.

Nous utilisons React Router pour diriger vers ces pages en fonction de l'URL, comme vous pouvez le voir dans le src/app.js fichier.


  
    
      
        
          
        
        
          
        
        
        
        
          
        
      
    
  

La seule autre chose à noter dans l'extrait ci-dessus est le SessionProvider, qui est un contexte React pour stocker les informations de l'utilisateur lors de la connexion. Nous reviendrons sur cela dans la section d'authentification. Pour l'instant, il suffit de savoir que cela nous donne accès aux informations du compte (et donc de l'utilisateur) de chaque composant.

Jetez un coup d'œil à la page d'accueil (src/pages/home.js) pour voir comment nous utilisons une combinaison de hooks pour gérer nos données. La majeure partie de la logique de notre application est implémentée dans les requêtes FaunaDB qui vivent dans le src/fauna/queriedossier s. Tous les appels à la base de données passent par le gestionnaire de requêtes, qui, dans un prochain article, sera refactorisé en appels de fonction sans serveur. Mais pour l'instant, ces appels proviennent du frontend et nous en sécuriserons les parties sensibles avec les règles de sécurité ABAC de FaunaDB et les fonctions définies par l'utilisateur (UDF). Étant donné que FaunaDB se comporte comme une API sécurisée par jeton, nous n'avons pas à nous soucier d'une limite sur la quantité de connexions comme nous le ferions dans les bases de données traditionnelles.

Le pilote JavaScript FaunaDB

Ensuite, jetez un œil à la src/fauna/query-manager.js fichier pour voir comment nous connectons FaunaDB à notre application en utilisant le pilote JavaScript de FaunaDB, qui est juste un module de noeud que nous avons tiré avec `npm install`. Comme pour tout module de noeud, nous l'importons dans notre application comme suit:

import faunadb from 'faunadb'

Et créez un client en fournissant un jeton.

this.client = new faunadb.Client({
  secret: token || this.bootstrapToken
})

Nous couvrirons un peu plus les jetons dans la section Authentification. Pour l'instant, créons des données!

Création de données

La logique de création d'un nouveau document Fweet se trouve dans le src/fauna/queries/fweets.js fichier. Les documents FaunaDB sont comme JSON, et chaque Fweet suit la même structure de base:

const data = {
  data: {
   message: message,
   likes: 0,
   refweets: 0,
   comments: 0,
   created: Now()
  }
}

le Now() est utilisée pour insérer l'heure de la requête afin que les Fweets dans le flux d'un utilisateur puissent être triés chronologiquement. Notez que FaunaDB place automatiquement des horodatages sur chaque entité de base de données pour les requêtes temporelles. Cependant, l'horodatage FaunaDB représente l'heure de la dernière mise à jour du document, pas l'heure de sa création, et le document est mis à jour à chaque fois qu'un Fweet est aimé; pour notre ordre de tri prévu, nous avons besoin du temps créé.

Ensuite, nous envoyons ces données à FaunaDB avec le Créer() une fonction. En fournissant Create() avec la référence à la collection Fweets utilisant Collection(‘fweets’), nous précisons où les données doivent aller.

const query = Create(Collection('fweets'), data )

Nous pouvons maintenant envelopper cette requête dans une fonction qui prend un paramètre de message et l'exécute en utilisant client.query() qui enverra la requête à la base de données. Seulement quand on appelle client.query() la requête sera-t-elle envoyée à la base de données et exécutée. Avant cela, nous combinons autant de fonctions FQL que nous voulons pour construire notre requête.

function createFweet(message, hashtags) {
   const data = …
   const query = …
   return client.query(query)
}

Notez que nous avons utilisé de vieilles variables JavaScript simples pour composer cette requête et que nous avons simplement appelé des fonctions. L'écriture de FQL concerne la composition des fonctions; vous construisez des requêtes en combinant de petites fonctions dans des expressions plus grandes. Cette approche fonctionnelle présente de très forts avantages. Il nous permet d'utiliser des fonctionnalités en langage natif telles que les variables JavaScript pour composer des requêtes, tout en écrivant des fonctions FQL d'ordre supérieur qui sont protégées contre l'injection.

Par exemple, dans la requête ci-dessous, nous ajoutons des hashtags au document avec un CreateHashtags() fonction que nous avons définie ailleurs en utilisant FQL.

const data = {
  data: {
    // ...
    hashtags: CreateHashtags(tags),
    likes: 0,
    // ... 
}

Le fonctionnement de FQL à partir du langage hôte du pilote (dans ce cas, JavaScript) fait de FQL un eDSL (langage spécifique au domaine intégré). Des fonctions comme CreateHashtags() se comportent comme une fonction FQL native dans la mesure où ce ne sont que des fonctions qui prennent en entrée. Cela signifie que nous pouvons facilement étendre le langage avec nos propres fonctions, comme dans cette bibliothèque FQL open source de la communauté Fauna.

Il est également important de noter que nous créons deux entités dans deux collections différentes, en une seule transaction. Ainsi, si / quand les choses tournent mal, il n'y a aucun risque que le Fweet soit créé mais pas les Hashtags. En termes plus techniques, FaunaDB est transactionnel et cohérent, que vous exécutiez des requêtes sur plusieurs collections ou non, une propriété rare dans les bases de données distribuées évolutives.

Ensuite, nous devons ajouter l'auteur à la requête. Tout d'abord, nous pouvons utiliser le Identity() Fonction FQL pour renvoyer une référence au document actuellement connecté. Comme indiqué précédemment dans la section de modélisation des données, ce document est du type Compte et est séparé des Utilisateurs pour prendre en charge l'authentification unique dans une phase ultérieure.

Ensuite, nous devons envelopper Identity() dans un Get() pour accéder au document de compte complet et pas seulement à la référence.

Get(Identity()) 

Enfin, nous enveloppons tout cela dans un Select() pour sélectionner data.user du document de compte et ajoutez-le au JSON de données.

const data = {
  data: {
    // ...
    hashtags: CreateHashtags(tags),
    author: Select(('data', 'user'), Get(Identity())),
    likes: 0,
    // ...
  }
}

Maintenant que nous avons construit la requête, rassemblons-la et appelons client.query(query) pour l'exécuter.

function createFweet(message, hashtags) {
 const data = {
   data: {
     message: message,
     likes: 0,
     refweets: 0,
     comments: 0,
     author: Select(('data', 'user'), Get(Identity())),
     hashtags: CreateHashtags(tags),
     created: Now()
   }
 }
 
 const query = Create(Collection('fweets'), data )
 return client.query(query)
}

En utilisant la composition fonctionnelle, vous pouvez facilement combiner toute votre logique avancée en une seule requête qui sera exécutée en une seule transaction. Découvrez le fichier src/fauna/queries/fweets.js pour voir le résultat final qui profite encore plus de la composition des fonctions pour ajouter une limitation de débit, etc.

Sécurisation de vos données avec les UDF et les rôles ABAC

Le lecteur attentif aura maintenant quelques réflexions sur la sécurité. Nous créons essentiellement des requêtes en JavaScript et appelons ces requêtes depuis le frontend. Qu'est-ce qui empêche un utilisateur malveillant de modifier ces requêtes?

FaunaDB fournit deux fonctionnalités qui nous permettent de sécuriser nos données: le contrôle d'accès basé sur les attributs (ABAC) et les fonctions définies par l'utilisateur (UDF). Avec ABAC, nous pouvons contrôler les collections ou entités auxquelles une clé ou un jeton spécifique peut accéder en écrivant des rôles.

Avec les FDU, nous pouvons pousser les instructions FQL vers la base de données en utilisant le CreateFunction().

CreateFunction({ 
  name: 'create_fweet', 
  body: , 
})

Une fois que la fonction est dans la base de données en tant qu’UDF, où l’application ne peut plus la modifier, nous appelons ensuite cet UDF depuis le frontal.

client.query(
  Call(Function('create_fweet'), message, hashTags)
)

Étant donné que la requête est désormais enregistrée dans la base de données (tout comme une procédure stockée), l'utilisateur ne peut plus la manipuler.

Un exemple de la façon dont les FDU peuvent être utilisés pour sécuriser un appel est que nous ne pas passer à l'auteur du Fweet. L'auteur du Fweet est dérivé de la fonction Identity () à la place, ce qui rend impossible pour un utilisateur d'écrire un Fweet au nom de quelqu'un.

Bien sûr, nous devons encore définir que l'utilisateur a accès pour appeler l'UDF. Pour cela, nous utiliserons un rôle ABAC très simple qui définit un groupe de membres de rôle et leurs privilèges. Ce rôle sera nommé logged_in_role, sa composition inclura tous les documents de la collection des comptes, et tous ces membres auront le privilège d'appeler le create_fweet UDF.

CreateRole(
  name: 'logged_in_role', 
  privileges: (
   {
     resource: q.Function('create_fweet'),
     actions: {
       call: true
     }
   }
  ),
  membership: ({ resource: Collection('accounts') }),
)

Nous savons maintenant que ces privilèges sont accordés à un compte, mais comment «devenir» un compte? En utilisant FaunaDB S'identifier() pour authentifier nos utilisateurs comme expliqué dans la section suivante.

Comment implémenter l'authentification dans FaunaDB

Nous venons de montrer un rôle qui donne aux comptes les autorisations d'appeler le create_fweets une fonction. Mais comment «devenir» un compte?.

Tout d'abord, nous créons un nouveau document de compte, stockant les informations d'identification avec toutes les autres données associées au compte (dans ce cas, l'adresse e-mail et la référence à l'utilisateur).

return Create(Collection('accounts'), {
  credentials: { password: password },
    data: {
      email: email,
      user: Select(('ref'), Var('user'))
    }
  })
}

On peut alors appeler Login() sur la référence du compte, qui récupère un jeton.

Login(
 Match( < Account reference > ,
    { password: password }
 )
)

Nous utilisons ce jeton dans le client pour usurper l'identité du compte. Étant donné que tous les comptes sont membres de la collection de comptes, ce jeton satisfait à l'exigence d'adhésion de la logged_in_role et est autorisé à appeler le create_fweet UDF.

Pour démarrer tout ce processus, nous avons deux rôles très importants.

  • bootstrap_role: ne peut appeler que le login et register UDF
  • logged_in_role: peut appeler d'autres fonctions telles que create_fweet

Le jeton que vous avez reçu lorsque vous avez exécuté le script de configuration est essentiellement une clé créée avec le bootstrap_role. Un client est créé avec ce jeton dans src/fauna/query-manager.js qui ne pourra que s'inscrire ou se connecter. Une fois connecté, nous utilisons le nouveau jeton renvoyé de Login() pour créer un nouveau client FaunaDB qui accorde désormais l'accès à d'autres fonctions UDF telles que create_fweet. La déconnexion signifie que nous revenons simplement au jeton d'amorçage. Vous pouvez voir ce processus dans le src/fauna/query-manager.js, ainsi que des exemples de rôles plus complexes dans le src/fauna/setup/roles.js fichier.

Comment implémenter la session dans React

Auparavant, dans la section «Création du frontal», nous avons mentionné le SessionProvider composant. Dans React, les fournisseurs appartiennent à un contexte React qui est un concept pour faciliter le partage de données entre différents composants. Ceci est idéal pour les données telles que les informations utilisateur dont vous avez besoin partout dans votre application. En insérant le SessionProvider dans le HTML au début, nous nous sommes assurés que chaque composant y aurait accès. Maintenant, la seule chose qu'un composant doit faire pour accéder aux détails de l'utilisateur est d'importer le contexte et d'utiliser le hook «useContext» de React.

import SessionContext from '../context/session'
import React, { useContext } from 'react'

// In your component
const sessionContext = useContext(SessionContext)
const { user } = sessionContext.state

Mais comment l'utilisateur se retrouve-t-il dans le contexte? Lorsque nous avons inclus le SessionProvider, nous avons transmis une valeur composée de l'état actuel et d'une fonction de répartition.

const (state, dispatch) = React.useReducer(sessionReducer, { user: null })
// ...

L'état est simplement l'état actuel et la fonction de répartition est appelée pour modifier le contexte. Cette fonction de répartition est en fait le cœur du contexte car la création d'un contexte implique uniquement l'appel React.createContext() qui vous donnera accès à un Provider et un Consumer.

const SessionContext = React.createContext({})
export const SessionProvider = SessionContext.Provider
export const SessionConsumer = SessionContext.Consumer
export default SessionContext

Nous pouvons voir que l'état et la répartition sont extraits de quelque chose que React appelle un réducteur (en utilisant React.useReducer), écrivons donc un réducteur.

export const sessionReducer = (state, action) => {
 switch (action.type) {
   case 'login': {
     return { user: action.data.user }
   }
   case 'register': {
     return { user: action.data.user }
   }
   case 'logout': {
     return { user: null }
   }
   default: {
     throw new Error(`Unhandled action type: ${action.type}`)
   }
 }
}

C'est la logique qui vous permet de changer le contexte. En substance, il reçoit une action et décide comment modifier le contexte en fonction de cette action. Dans mon cas, l'action est simplement un type avec une chaîne. Nous utilisons ce contexte pour conserver les informations des utilisateurs, ce qui signifie que nous les appelons lors d'une connexion réussie avec:

sessionContext.dispatch({ type: 'login', data: e })

Ajout de Cloudinary pour les médias

Lorsque nous avons créé un Fweet, nous n'avons pas encore pris en compte les actifs. FaunaDB est destiné à stocker des données d'application, pas des taches d'image ou des données vidéo. Cependant, nous pouvons facilement stocker les médias sur Cloudinary et conserver simplement un lien dans FaunaDB. Ce qui suit insère le script Cloudinary (dans app.js):

loadScript('https://widget.cloudinary.com/v2.0/global/all.js')

Nous créons ensuite un widget de téléchargement cloudinaire (dans src/components/uploader.js):

window.cloudinary.createUploadWidget(
  {
    cloudName: process.env.REACT_APP_LOCAL___CLOUDINARY_CLOUDNAME,
    uploadPreset: process.env.REACT_APP_LOCAL___CLOUDINARY_TEMPLATE,
  },
  (error, result) => {
    // ...
  }
)

Comme mentionné précédemment, vous devez fournir un nom et un modèle de cloud cloudinary dans les variables d'environnement (.env.local pour utiliser cette fonction. La création d'un compte Cloudinary est gratuite et une fois que vous avez un compte, vous pouvez récupérer le nom du cloud dashboard.

Vous avez également la possibilité d'utiliser des clés API pour sécuriser les téléchargements. Dans ce cas, nous téléchargeons directement à partir de l'extrémité avant afin que le téléchargement utilise un modèle public. Pour ajouter un modèle ou le modifier pour le rendre public, cliquez sur le équipement dans le menu du haut, accédez à Télécharger onglet, puis cliquez sur Ajouter un préréglage de téléchargement.

Vous pouvez également modifier le modèle ml_default et le rendre public.

Maintenant, nous appelons widget.open() lorsque notre bouton multimédia est cliqué.

const handleUploadClick = () => {
  widget.open()
}
 
return (
  
)

Cela nous fournit un petit bouton multimédia qui ouvrira le widget de téléchargement Cloudinary lorsque vous cliquerez dessus.

Lorsque nous créons le widget, nous pouvons également fournir des styles et des polices pour lui donner l'apparence de notre propre application comme nous l'avons fait ci-dessus (dans src/components/uploader.js):

const widget = window.cloudinary.createUploadWidget(
   {
     cloudName: process.env.REACT_APP_LOCAL___CLOUDINARY_CLOUDNAME,
     uploadPreset: process.env.REACT_APP_LOCAL___CLOUDINARY_TEMPLATE,
     styles: {
       palette: {
         window: '#E5E8EB',
         windowBorder: '#4A4A4A',
         tabIcon: '#000000',
         // ...
       },
       fonts: {

Une fois que nous avons téléchargé des médias sur Cloudinary, nous recevons un tas d'informations sur les médias téléchargés, que nous ajoutons ensuite aux données lorsque nous créons un Fweet.

On peut alors simplement utiliser les id (que Cloudinary appelle le publicId) avec la bibliothèque Cloudinary React (dans src/components/asset.js):

import { Image, Video, Transformation } from 'cloudinary-react'

Pour afficher l'image dans notre flux.

Lorsque vous utilisez l'ID, au lieu de l'URL directe, Cloudinary effectue toute une gamme d'optimisations pour fournir les médias dans le format le plus optimal possible. Par exemple, lorsque vous ajoutez une image vidéo comme suit:

Cloudinary réduira automatiquement la vidéo à une largeur de 600 pixels et la livrera en tant que WebM (VP9) aux navigateurs Chrome (482 Ko), MP4 (HEVC) aux navigateurs Safari (520 Ko) ou MP4 (H.264 ) aux navigateurs qui ne prennent en charge aucun format (821 Ko). Cloudinary effectue ces optimisations côté serveur, améliorant considérablement le temps de chargement des pages et l'expérience utilisateur globale.

Récupération des données

Nous avons montré comment ajouter des données. Maintenant, nous devons encore récupérer les données. Obtenir les données de notre flux Fwitter présente de nombreux défis. Nous devons le faire:

  • Obtenez des fweets des personnes que vous suivez dans un ordre spécifique (en tenant compte du temps et de la popularité)
  • Demandez à l'auteur du fweet d'afficher son image de profil et sa poignée
  • Obtenez les statistiques pour montrer combien de likes, refweets et commentaires il a
  • Obtenez les commentaires pour lister ceux sous le pied.
  • Obtenez des informations pour savoir si vous avez déjà aimé, refweeté ou commenté ce fweet spécifique.
  • S'il s'agit d'un refweet, obtenez le fweet d'origine.

Ce type de requête récupère les données de nombreuses collections différentes et nécessite une indexation / un tri avancé, mais commençons simplement. Comment obtient-on les Fweets? Nous commençons par obtenir une référence à la collection Fweets en utilisant le Collection() une fonction.

Collection('fweets')

Et nous enveloppons cela dans le Documents() pour obtenir toutes les références de document de la collection.

Documents(Collection('fweets'))

Nous paginons ensuite sur ces références.

Paginate(Documents(Collection('fweets')))

Paginate() nécessite quelques explications. Avant d'appeler Paginate(), nous avons eu une requête qui a renvoyé un ensemble hypothétique de données. Paginate() matérialise en fait ces données dans des pages d'entités que nous pouvons lire. FaunaDB exige que nous l'utilisions Paginate() fonction pour nous protéger contre l'écriture de requêtes inefficaces qui récupèrent chaque document d'une collection, car dans une base de données construite à grande échelle, cette collection pourrait contenir des millions de documents. Sans la sauvegarde de Paginate (), cela pourrait coûter très cher!

Enregistrons cette requête partielle dans une simple variable JavaScript references sur laquelle nous pouvons continuer de bâtir.

const references = Paginate(Documents(Collection('fweets')))

Jusqu'à présent, notre requête ne renvoie qu'une liste de références à nos Fweets. Pour obtenir les documents réels, nous faisons exactement ce que nous ferions en JavaScript: cartographier la liste avec une fonction anonyme. Dans FQL, une Lambda n'est qu'une fonction anonyme.

const fweets = Map(
  references,
  Lambda(('ref'), Get(Var('ref')))
)

Cela peut sembler détaillé si vous êtes habitué aux langages de requête déclaratifs comme SQL qui déclarent ce que tu veux et laissez la base de données comprendre comment l'obtenir. En revanche, FQL déclare à la fois ce que tu veux et Comment le veux-tu ce qui le rend plus procédural. Étant donné que vous définissez la façon dont vous souhaitez que vos données, et non le moteur de requête, l'impact sur le prix et les performances de votre requête est prévisible. Vous pouvez déterminer exactement combien de lectures cette requête coûte sans l'exécuter, ce qui est un avantage significatif si votre base de données contient une énorme quantité de données et est payante à l'utilisation. Il peut donc y avoir une courbe d'apprentissage, mais cela en vaut vraiment la peine et cela vous fera économiser. Et une fois que vous aurez appris le fonctionnement de FQL, vous constaterez que les requêtes se lisent exactement comme du code normal.

Préparons notre requête pour qu'elle soit étendue facilement en introduisant Let. Let nous permettra de lier des variables et de les réutiliser immédiatement dans la prochaine liaison de variables, ce qui vous permet de structurer votre requête de manière plus élégante.

const fweets = Map(
 references,
 Lambda(
   ('ref'),
   Let(
     {
       fweet: Get(Var('ref'))
     },
     // Just return the fweet for now
     Var('fweet')
   )
 )
)

Maintenant que nous avons cette structure, il est facile d'obtenir des données supplémentaires. Voyons donc l'auteur.

const fweets = Map(
 references,
 Lambda(
   ('ref'),
   Let(
     {
       fweet: Get(Var('ref')),
       author: Get(Select(('data', 'author'), Var('fweet')))
     },
     { fweet: Var('fweet'), author: Var('user') }
   )
 )
)

Bien que nous n'ayons pas écrit de jointure, nous venons de rejoindre Users (l'auteur) avec les Fweets.
Nous développerons ces éléments de base encore plus loin dans un article de suivi. En attendant, parcourez src/fauna/queries/fweets.js pour afficher la requête finale et plusieurs autres exemples.

Plus dans la base de code

Si vous ne l'avez pas déjà fait, veuillez ouvrir la base de code pour cet exemple d'application Fwitter. Vous trouverez une pléthore d'exemples bien commentés que nous n'avons pas explorés ici, mais qui le seront dans de futurs articles. Cette section aborde quelques fichiers que nous pensons que vous devriez vérifier.

Tout d'abord, consultez le src/fauna/queries/fweets.js fichier pour des exemples sur la façon de faire des correspondances et des tris complexes avec les index de FaunaDB (les index sont créés dans src/fauna/setup/fweets.js). Nous avons implémenté trois modèles d'accès différents pour obtenir des Fweets par popularité et par heure, par poignée et par balise.

Obtenir des Fweets par popularité et par temps est un modèle d'accès particulièrement intéressant car il trie les Fweets en fonction d'une sorte de popularité en décroissance en fonction des interactions des utilisateurs entre eux.

Consultez également src/fauna/queries/search.js, où nous avons implémenté la saisie semi-automatique basée sur les index FaunaDB et les liaisons d'index pour rechercher des auteurs et des tags. Étant donné que FaunaDB peut indexer plusieurs collections, nous pouvons écrire un index qui prend en charge un type de recherche à saisie semi-automatique sur les utilisateurs et les balises.

Nous avons implémenté ces exemples car la combinaison d'index flexibles et puissants avec des relations est rare pour les bases de données distribuées évolutives. Les bases de données qui manquent de relations et d'index flexibles vous obligent à connaître à l'avance comment vos données seront accessibles et vous rencontrerez des problèmes lorsque votre logique métier doit changer pour s'adapter aux cas d'utilisation évolutifs de vos clients.

Dans FaunaDB, si vous n'aviez pas prévu une manière spécifique d'accéder à vos données, pas de soucis – ajoutez simplement un Index! Nous avons des index de plage, des index de termes et des index composites qui peuvent être spécifiés quand vous le souhaitez sans avoir à coder pour une cohérence éventuelle.

Un aperçu de ce qui est à venir

Comme mentionné dans l'introduction, nous introduisons cette application Fwitter pour démontrer des cas d'utilisation complexes et réels. Cela dit, quelques fonctionnalités sont encore manquantes et seront couvertes dans les prochains articles, y compris le streaming, la pagination, les benchmarks et un modèle de sécurité plus avancé avec des jetons de courte durée, des jetons JWT, une connexion unique (éventuellement en utilisant un service comme Auth0 ), La limitation du débit basée sur IP (avec les employés Cloudflare), la vérification des e-mails (avec un service comme SendGrid) et les cookies HttpOnly.

Le résultat final sera une pile qui s'appuie sur des services et des fonctions sans serveur qui est très similaire à une application JAMstack dynamique, moins le générateur de site statique. Restez à l'écoute pour les articles de suivi et assurez-vous de vous abonner au blog Fauna et de surveiller CSS-Tricks pour plus d'articles liés à FaunaDB.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *