Créer des graphiques en temps réel avec GraphQL et Postgres

Publié: 2022-03-10
Résumé rapide ↬ Il n'y a pas de meilleur moyen de comprendre les données qu'en les visualisant avec des graphiques et des diagrammes. La communauté JS a d'excellents projets open source qui facilitent la visualisation des données, cependant, il n'y a pas eu de solution incontournable pour créer des backends en temps réel capables de sauvegarder ces graphiques et de les rendre en temps réel. Avec GraphQL (qui a une spécification bien définie pour les abonnements en temps réel), nous pouvons obtenir un backend en temps réel en quelques secondes et l'utiliser pour alimenter des graphiques en temps réel.

Les graphiques font partie intégrante de toute industrie qui traite des données. Les graphiques sont utiles dans le secteur du vote et des sondages, et ils nous aident également à mieux comprendre les différents comportements et caractéristiques des utilisateurs et des clients avec lesquels nous travaillons.

Pourquoi les graphiques en temps réel sont-ils si importants ? Eh bien, ils sont utiles dans les cas où de nouvelles données sont produites en continu ; par exemple, lorsque l'utilisation de séries en temps réel pour visualiser les cours des actions est une excellente utilisation pour les graphiques en temps réel. Dans ce didacticiel, j'expliquerai comment créer des graphiques en temps réel avec des technologies open source adaptées à cette tâche particulière.

Remarque : Ce tutoriel nécessite des connaissances de base de React et GraphQL.

Empiler

  1. PostgreSQLName
    Le but même de l'utilisation de Charts est de visualiser des données de volumes "énormes". Nous avons donc besoin d'une base de données qui gère efficacement les données volumineuses et fournit une API intuitive pour les restructurer. Les bases de données SQL nous permettent de créer des vues qui résument et agrégent les données pour nous. Nous utiliserons Postgres qui est une base de données éprouvée et très efficace. Il dispose également d'extensions open source sophistiquées telles que Timescale et PostGIS qui nous permettent de créer respectivement des graphiques basés sur la géolocalisation et des séries chronologiques. Nous utiliserons Timescale pour construire notre graphique de séries chronologiques.
  2. Moteur GraphQL
    Cet article concerne la création de graphiques en temps réel, et GraphQL est livré avec une spécification bien définie pour les abonnements en temps réel. Hasura GraphQL Engine est un serveur GraphQL open-source qui prend une connexion Postgres et vous permet d'interroger les données Postgres sur GraphQL en temps réel. Il est également livré avec une couche de contrôle d'accès qui vous aide à restreindre vos données en fonction de règles de contrôle d'accès personnalisées.
  3. GraphiqueJS
    ChartJS est une bibliothèque open source populaire et bien entretenue pour la création de graphiques avec JavaScript. Nous utiliserons chart.js avec son abstraction ReactJS react-chartjs-2 . Pourquoi React, c'est parce que React offre aux développeurs une API intuitive basée sur les événements. De plus, le flux de données unidirectionnel de React est idéal pour créer des graphiques basés sur les données.
Plus après saut! Continuez à lire ci-dessous ↓

Conditions

Pour ce didacticiel, vous aurez besoin des éléments suivants sur votre système :

  1. DockerCE
    Docker est un logiciel qui vous permet de conteneuriser vos applications. Une image docker est un paquet indépendant qui contient un logiciel avec ses dépendances et un système d'exploitation minimaliste. De telles images Docker peuvent techniquement être exécutées sur n'importe quelle machine sur laquelle Docker est installé. Vous aurez besoin de docker pour ce tutoriel.
    • En savoir plus sur Docker
    • Installer Docker
  2. npm : npm est le gestionnaire de packages pour JavaScript.

Démo

Nous allons construire le graphique de séries chronologiques en direct suivant qui montre la température maximale d'un emplacement par intervalles de 5 secondes au cours des 20 dernières minutes à partir du moment présent.

Démo GIF du graphique en temps réel
Démo GIF du graphique en temps réel

Configuration du back-end

Exécution des services

Le backend comprend une base de données Postgres, son extension d'échelle de temps et le moteur Hasura GraphQL. Faisons fonctionner la base de données et notre serveur GraphQL en exécutant les images docker respectives. Créez un fichier appelé docker-compose.yaml et collez-y ce contenu.

Remarque : docker-compose est un utilitaire permettant d'exécuter plusieurs images docker de manière déclarative.

 version: '2' services: timescale: image: timescale/timescaledb:latest-pg10 restart: always environment: POSTGRES_PASSWORD: postgrespassword volumes: - db_data:/var/lib/postgresql/data graphql-engine: image: hasura/graphql-engine:v1.0.0-alpha38 ports: - "8080:8080" depends_on: - "timescale" restart: always environment: HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgrespassword@timescale:5432/postgres HASURA_GRAPHQL_ACCESS_KEY: mylongsecretkey command: - graphql-engine - serve - --enable-console volumes: db_data:

Ce docker-compose.yaml contient la spécification de deux services :

  1. timescale
    Ceci est notre base de données Postgres avec l'extension Timescale installée. Il est configuré pour s'exécuter sur le port 5432.
  2. graphql-engine
    Il s'agit de notre instance Hasura GraphQL Engine, c'est-à-dire le serveur GraphQL qui pointe vers la base de données et donne des API GraphQL dessus. Il est configuré pour s'exécuter sur le port 8080, et le port 8080 est mappé sur le port 8080 de la machine sur laquelle ce conteneur docker s'exécute. Cela signifie que vous pouvez accéder à ce serveur GraphQL via localhost:8080 de la machine.

Exécutons ces conteneurs docker en exécutant la commande suivante partout où vous avez placé votre docker-compose.yaml .

 docker-compose up -d

Cette commande extrait les images Docker du cloud et les exécute dans l'ordre indiqué. Cela peut prendre quelques secondes en fonction de votre vitesse Internet. Une fois terminé, vous pouvez accéder à votre console GraphQL Engine à l'adresse https://localhost:8080/console .

Console du moteur Hasura GraphQL
Console du moteur Hasura GraphQL ( Grand aperçu )

Configuration de la base de données

Ensuite, créons une table appelée température qui stocke les valeurs des températures à différents moments. Accédez à l'onglet Données de la console et accédez à la section SQL . Créez notre table de temperature en exécutant ce bloc SQL :

 CREATE TABLE temperature ( temperature numeric not null, location text not null, recorded_at timestamptz not null default now() );

Cela crée une simple table Postgres dans la base de données. Mais nous souhaitons tirer parti du partitionnement des intervalles de temps de l'extension Timescale. Pour cela, il faut convertir cette table en hypertable de timescale en exécutant la commande SQL :

 SELECT create_hypertable('temperature', 'recorded_at');

Cette commande crée une hypertable qui est partitionnée par le temps dans le champ recorded_at .

Maintenant, puisque cette table est créée, nous pouvons directement commencer à faire des requêtes GraphQL dessus. Vous pouvez les essayer en cliquant sur l'onglet GraphiQL en haut. Essayez d'abord de faire une mutation :

 mutation { insert_temperature ( objects: [{ temperature: 13.4 location: "London" }] ) { returning { recorded_at temperature } } }

La mutation GraphQL ci-dessus insère une ligne dans le tableau des temperature . Essayez maintenant de faire une requête GraphQL pour vérifier si les données ont été insérées.

Essayez ensuite de faire une requête :

 query { temperature { recorded_at temperature location } }

J'espère que ça a fonctionné :)

Maintenant, la tâche qui nous incombe est de créer un graphique de séries chronologiques en direct qui montre la température maximale d'un emplacement par intervalles de 5 secondes au cours des 20 dernières minutes à partir du moment présent. Créons une vue qui nous donne exactement ces données.

 CREATE VIEW last_20_min_temp AS ( SELECT time_bucket('5 seconds', recorded_at) AS five_sec_interval, location, MAX(temperature) AS max_temp FROM temperature WHERE recorded_at > NOW() - interval '20 minutes' GROUP BY five_sec_interval, location ORDER BY five_sec_interval ASC );

Cette vue regroupe les données de la table des temperature dans des fenêtres de 5 secondes avec leur température max ( max_temp) . Le regroupement secondaire se fait à l'aide du champ de location . Toutes ces données ne concernent que les vingt dernières minutes à partir du moment présent.

C'est ça. Notre backend est configuré. Construisons maintenant un joli graphique en temps réel.

L'extrémité avant

Bonjour les abonnements GraphQL

Les abonnements GraphQL sont essentiellement des requêtes GraphQL "en direct". Ils fonctionnent sur WebSockets et ont exactement la même structure de réponse que les requêtes GraphQL. Revenez à https://localhost:8080/console et essayez de vous abonner à GraphQL pour la vue que nous avons créée.

 subscription { last_20_min_temp( order_by: { five_sec_interval: asc } where: { location: { _eq: "London" } } ) { five_sec_interval location max_temp } }

Cet abonnement s'abonne aux données dans la vue où l'emplacement est London et il est classé dans l'ordre croissant des five_second_intervals .

Naturellement, la réponse de la vue serait un tableau vide car nous n'avons rien inséré dans la base de données au cours des vingt dernières minutes. (Vous pourriez voir l'entrée que nous avons insérée il y a quelque temps si vous avez atteint cette section dans les vingt minutes.)

 { "data": { "last_20_min_temp": [] } }

En gardant cet abonnement activé, ouvrez un autre onglet et essayez d'insérer une autre valeur dans le tableau des temperatures en utilisant la même mutation que celle que nous avons effectuée précédemment. Après l'insertion, si vous revenez à l'onglet où était l'abonnement, vous verrez la réponse mise à jour automatiquement. C'est la magie en temps réel fournie par GraphQL Engine. Utilisons cet abonnement pour alimenter notre graphique en temps réel.

Premiers pas avec Create-React-App

Commençons rapidement avec un démarreur d'application React en utilisant créer une application de réaction. Exécutez la commande :

 npx create-react-app time-series-chart

Cela créera un projet de démarrage vide. cd dedans et installez les bibliothèques GraphQL et chart. Installez également le moment pour convertir les horodatages en un format lisible par l'homme.

 cd time-series-chart npm install --save apollo-boost apollo-link-ws subscriptions-transport-ws graphql react-apollo chart.js react-chartjs-2 moment

Enfin, exécutez l'application avec npm start et une application React de base s'ouvrira à https://localhost:3000 .

Raw créer-réagir-app
Raw creat-react-app ( Grand aperçu )

Configuration du client Apollo pour GraphQL côté client

Le client Apollo est actuellement le meilleur client GraphQL qui fonctionne avec n'importe quel serveur compatible GraphQL. Relay modern est bien aussi, mais le serveur doit prendre en charge la spécification de relais pour tirer parti de tous les avantages de Relay modern. Nous utiliserons le client Apollo pour GraphQL côté client pour ce didacticiel. Effectuons la configuration pour fournir le client Apollo à l'application.

Je n'entre pas dans les subtilités de cette configuration car les extraits de code suivants sont tirés directement de la documentation. Dirigez-vous vers src/index.js dans le répertoire de l'application React et instanciez le client Apollo et ajoutez cet extrait de code au-dessus ReactDOM.render .

 import { WebSocketLink } from 'apollo-link-ws'; import { ApolloClient } from 'apollo-client'; import { ApolloProvider } from 'react-apollo'; import { InMemoryCache } from 'apollo-cache-inmemory'; // Create a WebSocket link: const link = new WebSocketLink({ uri: `ws://localhost:8080/v1alpha1/graphql`, options: { reconnect: true, connectionParams: { headers: { "x-hasura-admin-secret: "mylongsecretkey" } } } }) const cache = new InMemoryCache(); const client = new ApolloClient({ link, cache });

Enfin, encapsulez l' App dans ApolloProvider afin que nous puissions utiliser le client Apollo dans les composants enfants. Votre App.js devrait enfin ressembler à :

 import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import { WebSocketLink } from 'apollo-link-ws'; import { ApolloClient } from 'apollo-client'; import { ApolloProvider } from 'react-apollo'; import { InMemoryCache } from 'apollo-cache-inmemory'; // Create a WebSocket link: const link = new WebSocketLink({ uri: `ws://localhost:8080/v1alpha1/graphql`, options: { reconnect: true, connectionParams: { headers: { "x-hasura-admin-secret: "mylongsecretkey" } } } }) const cache = new InMemoryCache(); const client = new ApolloClient({ link, cache }); ReactDOM.render( ( <ApolloProvider client={client}> <App /> </ApolloProvider> ), document.getElementById('root') );

Le client Apollo a été configuré. Nous pouvons désormais utiliser facilement GraphQL en temps réel depuis notre application. Dirigez-vous vers src/App.js .

Construire le graphique

ChartJS fournit une API assez soignée pour créer des graphiques. Nous allons construire un graphique en courbes ; donc un graphique en courbes attend des données de la forme :

 { "labels": ["label1", "label2", "label3", "label4"], "datasets": [{ "label": "Sample dataset", "data": [45, 23, 56, 55], "pointBackgroundColor": ["red", "brown", "green", "yellow"], "borderColor": "brown", "fill": false }], }

Si l'ensemble de données ci-dessus est utilisé pour afficher un graphique en courbes, il ressemblera à ceci :

Exemple de graphique linéaire
Exemple de graphique linéaire ( Grand aperçu )

Essayons d'abord de construire cet exemple de graphique. Importez Line de react-chartjs-2 et affichez-la en passant l'objet ci-dessus en tant qu'accessoire de données. La méthode de rendu ressemblerait à :

 render() { const data = { "labels": ["label1", "label2", "label3", "label4"], "datasets": [{ "label": "Sample dataset", "data": [45, 23, 56, 55], "pointBackgroundColor": ["red", "brown", "green", "yellow"], "borderColor": "brown", "fill": false }], } return ( <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '20px'}} > <Line data={data} /> </div> ); }

Ensuite, nous nous abonnerons aux données de notre vue et les alimenterons dans le graphique en courbes. Mais comment fait-on des abonnements sur le client ?

Les composants <Subscription> d'Apollo fonctionnent en utilisant le modèle de prop de rendu où les enfants d'un composant sont rendus avec le contexte des données d'abonnement.

 <Subscription subscription={gql`subscription { parent { child } }`} /> { ({data, error, loading}) => { if (error) return <Error error={error} />; if (loading) return <Loading />; return <RenderData data={data} />; } } </Subscription>

Utilisons un tel composant d' Subscription pour nous abonner à notre vue, puis transformons les données d'abonnement en la structure attendue par ChartJS. La logique de transformation ressemble à ceci :

 let chartJSData = { labels: [], datasets: [{ label: "Max temperature every five seconds", data: [], pointBackgroundColor: [], borderColor: 'brown', fill: false }] }; data.last_20_min_temp.forEach((item) => { const humanReadableTime = moment(item.five_sec_interval).format('LTS'); chartJSData.labels.push(humanReadableTime); chartJSData.datasets[0].data.push(item.max_temp); chartJSData.datasets[0].pointBackgroundColor.push('brown'); })

Remarque : Vous pouvez également utiliser la bibliothèque open source graphq2chartjs pour transformer les données de la réponse GraphQL en un formulaire attendu par ChartJS.

Après l'avoir utilisé dans le composant Subscription, notre App.js ressemble à :

 import React, { Component } from 'react'; import { Line } from 'react-chartjs-2'; import { Subscription } from 'react-apollo'; import gql from 'graphql-tag'; import moment from 'moment'; const TWENTY_MIN_TEMP_SUBSCRIPTION= gql' subscription { last_20_min_temp( order_by: { five_sec_interval: asc } where: { location: { _eq: "London" } } ) { five_sec_interval location max_temp } } ' class App extends Component { render() { return ( <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '20px'}} > <Subscription subscription={TWENTY_MIN_TEMP_SUBSCRIPTION}> { ({data, error, loading}) => { if (error) { console.error(error); return "Error"; } if (loading) { return "Loading"; } let chartJSData = { labels: [], datasets: [{ label: "Max temperature every five seconds", data: [], pointBackgroundColor: [], borderColor: 'brown', fill: false }] }; data.last_20_min_temp.forEach((item) => { const humanReadableTime = moment(item.five_sec_interval).format('LTS'); chartJSData.labels.push(humanReadableTime); chartJSData.datasets[0].data.push(item.max_temp); chartJSData.datasets[0].pointBackgroundColor.push('brown'); }) return ( <Line data={chartJSData} options={{ animation: {duration: 0}, scales: { yAxes: [{ticks: { min: 5, max: 20 }}]} }} /> ); } } </Subscription> </div> ); } } export default App;

Vous aurez un graphique en temps réel entièrement fonctionnel prêt à l' https://localhost:3000 . Cependant, il serait vide, alors remplissons quelques exemples de données afin que nous puissions réellement voir de la magie se produire.

Remarque : j'ai ajouté quelques options supplémentaires au graphique linéaire car je n'aime pas ces animations fantaisistes dans ChartJS. Une série chronologique a l'air sympa quand elle est simple, cependant, vous pouvez supprimer l'accessoire d'options si vous le souhaitez.

Insertion de données d'exemple

Écrivons un script qui remplit notre base de données avec des données factices. Créez un répertoire séparé (en dehors de cette application) et créez un fichier appelé script.js avec le contenu suivant,

 const fetch = require('node-fetch'); setInterval( () => { const randomTemp = (Math.random() * 5) + 10; fetch( `https://localhost:8080/v1alpha1/graphql`, { method: 'POST', body: JSON.stringify({ query: ` mutation ($temp: numeric) { insert_temperature ( objects: [{ temperature: $temp location: "London" }] ) { returning { recorded_at temperature } } } `, variables: { temp: randomTemp } }) } ).then((resp) => resp.json().then((respObj) => console.log(JSON.stringify(respObj, null, 2)))); }, 2000 );

Exécutez maintenant ces deux commandes :

 npm install --save node-fetch node script.js

Vous pouvez revenir à https://localhost:3000 et voir la mise à jour du graphique.

Finir

Vous pouvez créer la plupart des graphiques en temps réel en utilisant les idées dont nous avons discuté ci-dessus. L'algorithme est :

  1. Déployer le moteur GraphQL avec Postgres ;
  2. Créez des tables où vous souhaitez stocker des données ;
  3. Abonnez-vous à ces tables depuis votre application React ;
  4. Rendre le graphique.

Vous pouvez trouver le code source ici.