Créer des graphiques en temps réel avec GraphQL et Postgres
Publié: 2022-03-10Les 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
- 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. - 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. - GraphiqueJS
ChartJS est une bibliothèque open source populaire et bien entretenue pour la création de graphiques avec JavaScript. Nous utiliseronschart.js
avec son abstraction ReactJSreact-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.
Conditions
Pour ce didacticiel, vous aurez besoin des éléments suivants sur votre système :
- 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
- 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.
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 :
-
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. -
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 vialocalhost: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
.
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
.
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 :
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 :
- Déployer le moteur GraphQL avec Postgres ;
- Créez des tables où vous souhaitez stocker des données ;
- Abonnez-vous à ces tables depuis votre application React ;
- Rendre le graphique.
Vous pouvez trouver le code source ici.