Construirea graficelor în timp real cu GraphQL și Postgres
Publicat: 2022-03-10Graficele 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ă
- 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ă. - 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. - ChartJS
ChartJS este o bibliotecă open source populară și bine întreținută pentru construirea de diagrame cu JavaScript. Vom folosichart.js
împreună cu abstractizarea ReactJSreact-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.
Cerințe
Pentru acest tutorial, veți avea nevoie de următoarele pe sistemul dvs.:
- 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
- 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.
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:
-
timescale
Aceasta este baza noastră de date Postgres cu extensia Timescale instalată. Este configurat să ruleze la portul 5432. -
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 prinlocalhost: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
.
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
.
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:
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:
- Implementați motorul GraphQL cu Postgres;
- Creați tabele în care doriți să stocați date;
- Abonați-vă la aceste tabele din aplicația dvs. React;
- Redați diagrama.
Puteți găsi codul sursă aici.