Construirea graficelor în timp real cu GraphQL și Postgres

Publicat: 2022-03-10
Rezumat rapid ↬ Nu există o modalitate mai bună de a înțelege datele decât prin vizualizarea lor cu diagrame și diagrame. Comunitatea JS are niște proiecte open-source grozave care ușurează vizualizarea datelor, cu toate acestea, nu a existat o soluție ideală pentru construirea de backend-uri în timp real care să poată susține aceste diagrame și să le facă în timp real. Cu GraphQL (care are o specificație bine definită pentru abonamente în timp real), putem obține un backend în timp real care rulează în câteva secunde și îl putem folosi pentru a alimenta grafice în timp real.

Graficele fac parte integrantă din orice industrie care se ocupă de date. Diagramele sunt utile în industria votării și sondajelor și sunt, de asemenea, grozave pentru a ne ajuta să înțelegem mai bine diferitele comportamente și caracteristici ale utilizatorilor și clienților cu care lucrăm.

De ce sunt atât de importante diagramele în timp real? Ei bine, sunt utile în cazurile în care date noi sunt produse continuu; de exemplu, atunci când utilizați serii în timp real pentru vizualizarea prețurilor acțiunilor, este o utilizare excelentă pentru graficele în timp real. În acest tutorial, voi explica cum să construiți diagrame în timp real cu tehnologii open-source adecvate exact pentru această sarcină specială.

Notă : Acest tutorial necesită cunoștințe de bază despre React și GraphQL.

Grămadă

  1. PostgreSQL
    Scopul din spatele utilizării diagramelor este de a vizualiza date „uriașe”. Prin urmare, avem nevoie de o bază de date care să gestioneze eficient datele mari și să furnizeze un API intuitiv pentru a le restructura. Bazele de date SQL ne permit să facem vederi care abstracte și agreg date pentru noi. Vom folosi Postgres, care este o bază de date testată în timp și foarte eficientă. De asemenea, are extensii open-source de lux, cum ar fi Timescale și PostGIS, care ne permit să construim diagrame bazate pe geolocalizare și, respectiv, grafice bazate pe serii de timp. Vom folosi Timecale pentru a construi diagrama noastră de serie temporală.
  2. Motorul GraphQL
    Această postare este despre construirea de diagrame în timp real, iar GraphQL vine cu o specificație bine definită pentru abonamente în timp real. Hasura GraphQL Engine este un server GraphQL open-source care are o conexiune Postgres și vă permite să interogați datele Postgres prin GraphQL în timp real. De asemenea, vine cu un nivel de control al accesului care vă ajută să vă restricționați datele pe baza regulilor personalizate de control al accesului.
  3. ChartJS
    ChartJS este o bibliotecă open source populară și bine întreținută pentru construirea de diagrame cu JavaScript. Vom folosi chart.js împreună cu abstractizarea ReactJS react-chartjs-2 . Despre motivul pentru care React, este pentru că React oferă dezvoltatorilor un API intuitiv bazat pe evenimente. De asemenea, fluxul de date unidirecțional al React este ideal pentru construirea de diagrame bazate pe date.
Mai multe după săritură! Continuați să citiți mai jos ↓

Cerințe

Pentru acest tutorial, veți avea nevoie de următoarele pe sistemul dvs.:

  1. Docker CE
    Docker este un software care vă permite să vă containerizați aplicațiile. O imagine docker este un pachet independent care conține software împreună cu dependențele sale și un sistem de operare minimalist. Astfel de imagini docker pot fi rulate din punct de vedere tehnic pe orice mașină care are docker instalat. Veți avea nevoie de docker pentru acest tutorial.
    • Citiți mai multe despre Docker
    • Instalați Docker
  2. npm: npm este pachetul de gestionare pentru JavaScript.

Demo

Vom construi următorul grafic în timp real care arată temperatura maximă a unei locații la intervale de 5 secunde în ultimele 20 de minute din momentul prezent.

GIF Demo a graficului în timp real
GIF Demo a graficului în timp real

Configurarea backend-ului

Rularea Serviciilor

Backend-ul constă dintr-o bază de date Postgres, extensia sa de timp și Hasura GraphQL Engine. Să punem în funcțiune baza de date și serverul nostru GraphQL, rulând imaginile docker respective. Creați un fișier numit docker-compose.yaml și inserați acest conținut în el.

Notă : docker-compose este un utilitar pentru a rula mai multe imagini docker în mod declarativ.

 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:

Acest docker-compose.yaml conține specificațiile pentru două servicii:

  1. timescale
    Aceasta este baza noastră de date Postgres cu extensia Timescale instalată. Este configurat să ruleze la portul 5432.
  2. graphql-engine
    Aceasta este instanța noastră Hasura GraphQL Engine, adică serverul GraphQL care indică baza de date și oferă API-uri GraphQL peste aceasta. Este configurat să ruleze la portul 8080, iar portul 8080 este mapat la portul 8080 al mașinii pe care rulează acest container docker. Aceasta înseamnă că puteți accesa acest server GraphQL prin localhost:8080 al mașinii.

Să rulăm aceste containere docker rulând următoarea comandă oriunde ați plasat docker-compose.yaml .

 docker-compose up -d

Această comandă extrage imaginile docker din cloud și le rulează în ordinea dată. Ar putea dura câteva secunde în funcție de viteza dvs. de internet. După ce este finalizat, puteți accesa consola GraphQL Engine la https://localhost:8080/console .

Consola de motor Hasura GraphQL
Consola Hasura GraphQL Engine (previzualizare mare)

Configurarea bazei de date

În continuare, să creăm un tabel numit temperatură care stochează valorile temperaturilor în momente diferite. Accesați fila Date din consolă și accesați secțiunea SQL . Creați tabelul nostru de temperature rulând acest bloc SQL:

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

Acest lucru creează un tabel Postgres simplu în baza de date. Dar dorim să valorificăm partiționarea pe intervale de timp a extensiei Timecale. Pentru a face acest lucru, trebuie să convertim acest tabel în hipertabelul scalei de timp rulând comanda SQL:

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

Această comandă creează un hypertable care este partiționat după timp în câmpul recorded_at .

Acum, deoarece acest tabel este creat, putem începe direct să facem interogări GraphQL peste el. Le puteți încerca făcând clic pe fila GraphiQL de sus. Încercați mai întâi să faceți o mutație:

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

Mutația GraphQL de mai sus inserează un rând în tabelul de temperature . Acum încercați să faceți o interogare GraphQL pentru a verifica dacă datele au fost inserate.

Apoi încercați să faceți o interogare:

 query { temperature { recorded_at temperature location } }

Sper ca a functionat :)

Acum, sarcina noastră este să creăm o diagramă în timp real care să arate temperatura maximă a unei locații la intervale de 5 secunde în ultimele 20 de minute din momentul prezent. Să creăm o vizualizare care ne oferă exact aceste date.

 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 );

Această vizualizare grupează datele din tabelul de temperature în ferestre de 5 secunde cu temperatura lor maximă ( max_temp) . Gruparea secundară se face folosind câmpul location . Toate aceste date sunt doar din ultimele douăzeci de minute din momentul prezent.

Asta e. Backend-ul nostru este configurat. Să construim acum o diagramă frumoasă în timp real.

În față

Bună ziua, Abonamente GraphQL

Abonamentele GraphQL sunt în esență interogări GraphQL „în direct”. Ele operează prin WebSockets și au exact aceeași structură de răspuns ca și interogările GraphQL. Reveniți la https://localhost:8080/console și încercați să faceți un abonament GraphQL la vizualizarea creată de noi.

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

Acest abonament se abonează la datele din vizualizarea unde locația este London și este ordonat în ordinea crescătoare a intervalelor de five_second_intervals .

Desigur, răspunsul din vizualizare ar fi o matrice goală, deoarece nu am inserat nimic în baza de date în ultimele douăzeci de minute. (Este posibil să vedeți intrarea pe care am inserat-o cândva în urmă dacă ați ajuns la această secțiune în douăzeci de minute.)

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

Păstrând acest abonament activat, deschideți o altă filă și încercați să introduceți o altă valoare în tabelul de temperatures folosind aceeași mutație pe care am efectuat-o mai devreme. După inserare, dacă reveniți la fila în care era activat abonamentul, veți vedea că răspunsul s-a actualizat automat. Aceasta este magia în timp real pe care o oferă GraphQL Engine. Să folosim acest abonament pentru a ne alimenta graficul în timp real.

Noțiuni introductive cu Create-React-App

Să începem rapid cu un starter al aplicației React folosind aplicația create react. Rulați comanda:

 npx create-react-app time-series-chart

Acest lucru va crea un proiect de pornire gol. cd în el și instalați bibliotecile GraphQL și diagrame. De asemenea, instalați moment pentru conversia marcajelor de timp într-un format care poate fi citit de om.

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

În cele din urmă, rulați aplicația cu npm start și o aplicație React de bază se va deschide la https://localhost:3000 .

Aplicație brută de creare-reacție
Aplicație brută creat-react (previzualizare mare)

Configurarea clientului Apollo pentru GraphQL pe partea clientului

Clientul Apollo este în prezent cel mai bun client GraphQL care funcționează cu orice server compatibil GraphQL. Relay modern este și el bun, dar serverul trebuie să accepte specificațiile releu pentru a profita de toate beneficiile Relay modern. Vom folosi clientul Apollo pentru GraphQL la nivelul clientului pentru acest tutorial. Să efectuăm configurarea pentru a furniza clientul Apollo aplicației.

Nu intru în subtilitățile acestei configurații, deoarece următoarele fragmente de cod sunt preluate direct din documente. Mergeți la src/index.js în directorul aplicației React și instanțiați clientul Apollo și adăugați acest fragment de cod deasupra 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 });

În cele din urmă, împachetați App în ApolloProvider , astfel încât să putem folosi clientul Apollo în componentele pentru copii. App.js -ul dvs. ar trebui să arate astfel:

 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') );

Clientul Apollo a fost configurat. Acum putem folosi cu ușurință GraphQL în timp real din aplicația noastră. Mergeți la src/App.js .

Construirea diagramei

ChartJS oferă un API destul de îngrijit pentru construirea de diagrame. Vom construi o diagramă cu linii; deci o diagramă cu linii așteaptă date de forma:

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

Dacă setul de date de mai sus este folosit pentru redarea unei diagrame cu linii, ar arăta cam așa:

Exemplu de diagramă cu linii
Exemplu de diagramă cu linii (previzualizare mare)

Să încercăm mai întâi să construim acest exemplu de diagramă. Importați Line din react-chartjs-2 și redați-o trecând obiectul de mai sus ca prop de date. Metoda de randare ar arăta cam așa:

 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> ); }

În continuare, ne vom abona la datele din vizualizarea noastră și le vom introduce în diagrama cu linii. Dar cum efectuăm abonamentele pe client?

Componentele <Subscription> ale lui Apollo funcționează folosind modelul de prop de randare în care copiii unei componente sunt randați în contextul datelor abonamentului.

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

Să folosim o astfel de componentă de Subscription pentru a ne abona la vizualizarea noastră și apoi să transformăm datele abonamentului în structura la care se așteaptă ChartJS. Logica de transformare arată astfel:

 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'); })

Notă : De asemenea, puteți utiliza biblioteca open-source graphq2chartjs pentru a transforma datele din răspunsul GraphQL într-o formă la care se așteaptă ChartJS.

După ce ați folosit acest lucru în cadrul componentei Abonament, App.js arată astfel:

 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;

Veți avea gata o diagramă în timp real complet funcțională la https://localhost:3000 . Cu toate acestea, ar fi gol, așa că haideți să populam câteva date eșantion, astfel încât să putem vedea de fapt ceva magie care se întâmplă.

Notă : am adăugat mai multe opțiuni la diagrama cu linii, deoarece nu-mi plac acele animații de lux din ChartJS. O serie cronologică arată dulce atunci când este simplă, totuși, puteți elimina opțiunile de prop dacă doriți.

Inserarea datelor mostre

Să scriem un script care populează baza noastră de date cu date fictive. Creați un director separat (în afara acestei aplicații) și creați un fișier numit script.js cu următorul conținut,

 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 );

Acum rulați aceste două comenzi:

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

Puteți reveni la https://localhost:3000 și puteți vedea actualizarea diagramei.

Terminand

Puteți construi majoritatea diagramelor în timp real folosind ideile pe care le-am discutat mai sus. Algoritmul este:

  1. Implementați motorul GraphQL cu Postgres;
  2. Creați tabele în care doriți să stocați date;
  3. Abonați-vă la aceste tabele din aplicația dvs. React;
  4. Redați diagrama.

Puteți găsi codul sursă aici.