Come creare un'app in tempo reale con gli abbonamenti GraphQL su Postgres
Pubblicato: 2022-03-10In questo articolo, daremo un'occhiata alle sfide legate alla creazione di applicazioni in tempo reale e al modo in cui gli strumenti emergenti le stanno affrontando con soluzioni eleganti su cui è facile ragionare. Per fare ciò, creeremo un'app di polling in tempo reale (come un sondaggio di Twitter con statistiche generali in tempo reale) semplicemente utilizzando Postgres, GraphQL, React e nessun codice di backend!
L'obiettivo principale sarà la configurazione del backend (distribuzione degli strumenti pronti per l'uso, la modellazione dello schema) e gli aspetti dell'integrazione del frontend con GraphQL e meno sull'interfaccia utente/UX del frontend (una certa conoscenza di ReactJS aiuterà). La sezione del tutorial adotterà un approccio paint-by-number, quindi cloneremo semplicemente un repository GitHub per la modellazione dello schema e l'interfaccia utente e la modificheremo, invece di creare l'intera app da zero.
Tutte le cose GraphQL
Sai tutto ciò che devi sapere su GraphQL? Se hai dei dubbi, Eric Baer ti copre con una guida dettagliata sulle sue origini, i suoi svantaggi e le basi su come lavorarci. Leggi un articolo correlato →
Prima di continuare a leggere questo articolo, vorrei ricordare che una conoscenza pratica delle seguenti tecnologie (o sostituti) è vantaggiosa:
- ReactJS
Questo può essere sostituito con qualsiasi framework frontend, Android o IOS seguendo la documentazione della libreria client. - Postgres
Puoi lavorare con altri database ma con strumenti diversi, i principi delineati in questo post continueranno ad essere applicati.
Puoi anche adattare molto facilmente questo contesto tutorial per altre app in tempo reale.
Come illustrato dal payload di GraphQL in basso, ci sono tre caratteristiche principali che dobbiamo implementare:
- Recupera la domanda del sondaggio e un elenco di opzioni (in alto a sinistra).
- Consenti a un utente di votare per una determinata domanda del sondaggio (il pulsante "Vota").
- Recupera i risultati del sondaggio in tempo reale e visualizzali in un grafico a barre (in alto a destra; possiamo sorvolare sulla funzione per recuperare un elenco degli utenti attualmente online poiché è una replica esatta di questo caso d'uso).
Sfide con la creazione di app in tempo reale
La creazione di app in tempo reale (soprattutto come sviluppatore frontend o qualcuno che è recentemente passato a diventare uno sviluppatore fullstack) è un problema di ingegneria difficile da risolvere. Questo è generalmente il modo in cui funzionano le app in tempo reale contemporanee (nel contesto della nostra app di esempio):
- Il frontend aggiorna un database con alcune informazioni; Il voto di un utente viene inviato al back-end, ovvero sondaggio/opzione e informazioni sull'utente (
user_id
,option_id
). - Il primo aggiornamento attiva un altro servizio che aggrega i dati del sondaggio per restituire un output che viene ritrasmesso all'app in tempo reale (ogni volta che qualcuno esprime un nuovo voto; se ciò avviene in modo efficiente, vengono elaborati solo i dati del sondaggio aggiornato e vengono aggiornati solo i clienti che si sono iscritti a questo sondaggio):
- I dati di voto vengono prima elaborati da un servizio
register_vote
(supponiamo che qui avvenga una convalida) che attiva un serviziopoll_results
. - I dati dei sondaggi aggregati in tempo reale vengono inoltrati dal servizio
poll_results
al frontend per la visualizzazione delle statistiche generali.
- I dati di voto vengono prima elaborati da un servizio
Questo modello è derivato da un approccio tradizionale di creazione di API e, di conseguenza, presenta problemi simili:
- Qualsiasi passaggio sequenziale potrebbe andare storto, lasciando la UX sospesa e influenzando altre operazioni indipendenti.
- Richiede molto impegno a livello di API in quanto è un unico punto di contatto per l'app frontend, che interagisce con più servizi. Deve anche implementare un'API in tempo reale basata su websocket: non esiste uno standard universale per questo e quindi vede un supporto limitato per l'automazione negli strumenti.
- L'app frontend è necessaria per aggiungere l'impianto idraulico necessario per utilizzare l'API in tempo reale e potrebbe anche dover risolvere il problema di coerenza dei dati tipico delle app in tempo reale (meno importante nel nostro esempio scelto, ma fondamentale per ordinare i messaggi in modo reale -app di chat a tempo).
- Molte implementazioni ricorrono all'utilizzo di database non relazionali aggiuntivi sul lato server (Firebase, ecc.) per un facile supporto delle API in tempo reale.
Diamo un'occhiata a come GraphQL e gli strumenti associati affrontano queste sfide.
Che cos'è GraphQL?
GraphQL è una specifica per un linguaggio di query per le API e un runtime lato server per l'esecuzione di query. Questa specifica è stata sviluppata da Facebook per accelerare lo sviluppo di app e fornire un formato di accesso ai dati standardizzato e indipendente dal database. Qualsiasi server GraphQL conforme alle specifiche deve supportare quanto segue:
- Richieste di letture
Un tipo di richiesta per richiedere dati nidificati da un'origine dati (che può essere una o una combinazione di un database, un'API REST o un altro schema/server GraphQL). - Mutazioni per le scritture
Un tipo di richiesta per scrivere/trasmettere dati nelle suddette origini dati. - Abbonamenti per le query dal vivo
Un tipo di richiesta per i clienti di iscriversi agli aggiornamenti in tempo reale.
GraphQL utilizza anche uno schema tipizzato. L'ecosistema ha molti strumenti che ti aiutano a identificare gli errori in fase di sviluppo/compilazione, il che si traduce in un minor numero di bug di runtime.
Ecco perché GraphQL è ottimo per le app in tempo reale:
- Le query live (abbonamenti) sono una parte implicita della specifica GraphQL. Qualsiasi sistema GraphQL deve disporre di funzionalità API native in tempo reale.
- Una specifica standard per le query in tempo reale ha consolidato gli sforzi della comunità in merito agli strumenti lato client, risultando in un modo molto intuitivo di integrazione con le API GraphQL.
GraphQL e una combinazione di strumenti open source per eventi di database e funzioni serverless/cloud offrono un ottimo substrato per la creazione di applicazioni cloud native con logica aziendale asincrona e funzionalità in tempo reale facili da creare e gestire. Questo nuovo paradigma si traduce anche in una grande esperienza per utenti e sviluppatori.
Nel resto di questo articolo, utilizzerò strumenti open source per creare un'app basata su questo diagramma di architettura:
Creazione di un'app per sondaggi/votazioni in tempo reale
Con questa introduzione a GraphQL, torniamo alla creazione dell'app di polling come descritto nella prima sezione.
Le tre funzionalità (o storie evidenziate) sono state scelte per dimostrare i diversi tipi di richieste GraphQL che la nostra app farà:
- Domanda
Recupera la domanda del sondaggio e le sue opzioni. - Mutazione
Consenti a un utente di esprimere un voto. - Sottoscrizione
Visualizza una dashboard in tempo reale per i risultati del sondaggio.
Prerequisiti
- Un account Heroku (usa il livello gratuito, non è richiesta la carta di credito)
Per distribuire un backend GraphQL (vedi punto successivo sotto) e un'istanza Postgres. - Motore Hasura GraphQL (gratuito, open source) Un server GraphQL pronto per l'uso su Postgres.
- Client Apollo (SDK open source gratuito)
Per integrare facilmente le app dei client con un server GraphQL. - npm (gestore di pacchetti open source gratuito)
Per eseguire la nostra app React.
Distribuzione del database e di un backend GraphQL
Distribuiremo un'istanza di Postgres e GraphQL Engine sul livello gratuito di Heroku. Possiamo usare un elegante pulsante Heroku per farlo con un solo clic.
Nota: puoi anche seguire questo link o cercare la documentazione relativa alla distribuzione di Hasura GraphQL per Heroku (o altre piattaforme).
Non avrai bisogno di alcuna configurazione aggiuntiva e puoi semplicemente fare clic sul pulsante "Distribuisci app". Una volta completata la distribuzione, prendere nota dell'URL dell'app:
<app-name>.herokuapp.com
Ad esempio, nello screenshot sopra, sarebbe:
hge-realtime-app-tutorial.herokuapp.com
Quello che abbiamo fatto finora è distribuire un'istanza di Postgres (come componente aggiuntivo nel gergo di Heroku) e un'istanza di GraphQL Engine che è configurata per utilizzare questa istanza di Postgres. Di conseguenza, ora abbiamo un'API GraphQL pronta per l'uso ma, poiché non abbiamo tabelle o dati nel nostro database, questo non è ancora utile. Quindi, affrontiamo questo problema immediatamente.
Modellazione dello schema del database
Il diagramma dello schema seguente acquisisce un semplice schema di database relazionale per la nostra app di sondaggio:
Come puoi vedere, lo schema è semplice e normalizzato che sfrutta i vincoli di chiave esterna. Sono questi vincoli che vengono interpretati dal motore GraphQL come relazioni 1:1 o 1:molti (ad es poll:options
è una relazione 1:molti poiché ogni sondaggio avrà più di 1 opzione che sono collegate dal vincolo di chiave esterna tra le id
della tabella poll
e la colonna poll_id
nella tabella delle option
). I dati correlati possono essere modellati come un grafico e possono quindi alimentare un'API GraphQL. Questo è esattamente ciò che fa il motore GraphQL.
Sulla base di quanto sopra, dovremo creare le seguenti tabelle e vincoli per modellare il nostro schema:
-
Poll
Una tabella per catturare la domanda del sondaggio. -
Option
Opzioni per ogni sondaggio. -
Vote
Per registrare il voto di un utente. - Vincolo di chiave esterna tra i seguenti campi (
table : column
):-
option : poll_id → poll : id
-
vote : poll_id → poll : id
-
vote : created_by_user_id → user : id
-
Ora che abbiamo il nostro schema di progettazione, implementiamolo nel nostro database Postgres. Per visualizzare istantaneamente questo schema, ecco cosa faremo:
- Scarica la CLI del motore GraphQL.
- Clona questo repository:
$ git clone clone https://github.com/hasura/graphql-engine $ cd graphql-engine/community/examples/realtime-poll
- Vai su
hasura/
e modificaconfig.yaml
:endpoint: https://<app-name>.herokuapp.com
- Applica le migrazioni utilizzando la CLI, dall'interno della directory del progetto (che hai appena scaricato tramite clonazione):
$ hasura migrate apply
Questo è tutto per il back-end. È ora possibile aprire la console di GraphQL Engine e verificare che tutte le tabelle siano presenti (la console è disponibile all'indirizzo https://<app-name>.herokuapp.com/console
).
Nota: avresti anche potuto utilizzare la console per implementare lo schema creando singole tabelle e quindi aggiungendo vincoli utilizzando un'interfaccia utente. L'uso del supporto integrato per le migrazioni in GraphQL Engine è solo un'opzione conveniente disponibile perché il nostro repository di esempio ha migrazioni per visualizzare le tabelle richieste e configurare relazioni/vincoli (questo è anche altamente raccomandato indipendentemente dal fatto che tu stia costruendo un hobby progetto o un'app pronta per la produzione).
Integrazione dell'app Frontend React con il backend GraphQL
Il frontend in questo tutorial è una semplice app che mostra la domanda del sondaggio, l'opzione per votare e i risultati aggregati del sondaggio in un unico posto. Come accennato in precedenza, ci concentreremo innanzitutto sull'esecuzione di questa app in modo da ottenere la gratificazione immediata dell'utilizzo della nostra API GraphQL recentemente distribuita, vedere come i concetti di GraphQL che abbiamo esaminato in precedenza in questo articolo alimentano i diversi casi d'uso di tale app , quindi esplora come funziona l'integrazione di GraphQL sotto il cofano.
NOTA: se non conosci ReactJS, potresti voler dare un'occhiata ad alcuni di questi articoli. Non entreremo nei dettagli della parte React dell'app e, invece, ci concentreremo maggiormente sugli aspetti GraphQL dell'app. Puoi fare riferimento al codice sorgente nel repository per tutti i dettagli su come è stata creata l'app React .
Configurazione dell'app front-end
- Nel repository clonato nella sezione precedente, modifica
HASURA_GRAPHQL_ENGINE_HOSTNAME
nel file src/apollo.js (all'interno della cartella/community/examples/realtime-poll
) e impostalo sull'URL dell'app Heroku dall'alto:export const HASURA_GRAPHQL_ENGINE_HOSTNAME = 'random-string-123.herokuapp.com';
- Vai alla radice del repository/cartella-app (
/realtime-poll/
) e usa npm per installare i moduli prerequisiti, quindi esegui l'app:$ npm install $ npm start
Dovresti essere in grado di giocare con l'app ora. Vai avanti e vota tutte le volte che vuoi, noterai che i risultati cambiano in tempo reale. Infatti, se imposti un'altra istanza di questa interfaccia utente e la punti allo stesso back-end, sarai in grado di vedere i risultati aggregati in tutte le istanze.
Quindi, in che modo questa app utilizza GraphQL? Continuare a leggere.
Dietro le quinte: GraphQL
In questa sezione, esploreremo le funzionalità di GraphQL che alimentano l'app, seguite da una dimostrazione della facilità di integrazione nella prossima.
La componente del sondaggio e il grafico dei risultati aggregati
Il componente del sondaggio in alto a sinistra che recupera un sondaggio con tutte le sue opzioni e acquisisce il voto di un utente nel database. Entrambe queste operazioni vengono eseguite utilizzando l'API GraphQL. Per recuperare i dettagli di un sondaggio, facciamo una query (ricordate questo dall'introduzione di GraphQL?):
query { poll { id question options { id text } } }
Usando il componente Mutation di react-apollo
, possiamo collegare la mutazione a un modulo HTML in modo tale che la mutazione venga eseguita utilizzando le variabili optionId
e userId
quando il modulo viene inviato:
mutation vote($optionId: uuid!, $userId: uuid!) { insert_vote(objects: [{option_id: $optionId, created_by_user_id: $userId}]) { returning { id } } }
Per mostrare i risultati del sondaggio, dobbiamo ricavare il conteggio dei voti per opzione dai dati nella tabella dei voti. Possiamo creare una vista Postgres e tracciarla utilizzando GraphQL Engine per rendere disponibili questi dati derivati su GraphQL.
CREATE VIEW poll_results AS SELECT poll.id AS poll_id, o.option_id, count(*) AS votes FROM (( SELECT vote.option_id, option.poll_id, option.text FROM ( vote LEFT JOIN public.option ON ((option.id = vote.option_id)))) o LEFT JOIN poll ON ((poll.id = o.poll_id))) GROUP BY poll.question, o.option_id, poll.id;
La vista poll_results
unisce i dati delle tabelle dei vote
e poll
per fornire un conteggio aggregato del numero di voti per ciascuna opzione.
Utilizzando GraphQL Subscriptions su questa vista, react-google-charts e il componente di abbonamento di react-apollo
, possiamo collegare un grafico reattivo che si aggiorna in tempo reale quando si verifica un nuovo voto da qualsiasi cliente.
subscription getResult($pollId: uuid!) { poll_results(where: {poll_id: {_eq: $pollId}}) { option { id text } votes } }
Integrazione API GraphQL
Come accennato in precedenza, ho utilizzato Apollo Client, un SDK open source per integrare un'app ReactJS con il backend GraphQL. Apollo Client è analogo a qualsiasi libreria client HTTP come le richieste per python, il modulo http standard per JavaScript e così via. Incapsula i dettagli per effettuare una richiesta HTTP (in questo caso richieste POST). Utilizza la configurazione (specificata in src/apollo.js
) per effettuare richieste di query/mutazione/abbonamento (specificata in src/GraphQL.jsx con l'opzione di utilizzare variabili che possono essere sostituite dinamicamente nel codice JavaScript della tua app REACT) per un endpoint GraphQL. Sfrutta inoltre lo schema tipizzato dietro l'endpoint GraphQL per fornire la convalida in fase di compilazione/sviluppo per le richieste sopra menzionate. Vediamo quanto è facile per un'app client effettuare una richiesta di query live (abbonamento) all'API GraphQL.
Configurazione dell'SDK
L'Apollo Client SDK deve essere puntato su un server GraphQL, in modo che possa gestire automaticamente il codice standard in genere necessario per tale integrazione. Quindi, questo è esattamente ciò che abbiamo fatto quando abbiamo modificato src/apollo.js durante la configurazione dell'app frontend.
Effettuare una richiesta di abbonamento GraphQL (Live-Query)
Definisci la sottoscrizione che abbiamo esaminato nella sezione precedente nel file src/GraphQL.jsx :
const SUBSCRIPTION_RESULT = ` subscription getResult($pollId: uuid!) { poll_results ( order_by: option_id_desc, where: { poll_id: {_eq: $pollId} } ) { option_id option { id text } votes } }`;
Useremo questa definizione per collegare il nostro componente React:
export const Result = (pollId) => ( <Subscription subscription={gql`${SUBSCRIPTION_RESULT}`} variables={pollId}> {({ loading, error, data }) => { if (loading) return
Caricamento in corso...</p>; se (errore) ritorna
Errore :</p>; Restituzione ( <div> <div> {renderGrafico(dati)} </div> </div> ); }} </Abbonamento> )
Una cosa da notare qui è che l'abbonamento di cui sopra potrebbe anche essere stato una query. La semplice sostituzione di una parola chiave con un'altra ci dà una "query live", ed è tutto ciò che serve per l'Apollo Client SDK per agganciare questa API in tempo reale con la tua app. Ogni volta che c'è un nuovo set di dati dalla nostra query live, l'SDK attiva un nuovo rendering del nostro grafico con questi dati aggiornati (usando la renderChart(data)
). Questo è tutto. E 'davvero così semplice!
Pensieri finali
In tre semplici passaggi (creazione di un backend GraphQL, modellazione dello schema dell'app e integrazione del frontend con l'API GraphQL), puoi collegare rapidamente un'app in tempo reale completamente funzionale, senza rimanere impantanato in dettagli non necessari come la configurazione una connessione WebSocket. Proprio lì c'è il potere degli strumenti della comunità che supportano un'astrazione come GraphQL.
Se lo hai trovato interessante e desideri esplorare ulteriormente GraphQL per il tuo prossimo progetto parallelo o app di produzione, ecco alcuni fattori che potresti voler utilizzare per creare la tua toolchain GraphQL:
- Prestazioni e scalabilità
GraphQL è pensato per essere consumato direttamente dalle app front-end (non è meglio di un ORM nel back-end; in questo modo derivano reali vantaggi in termini di produttività). Quindi i tuoi strumenti devono essere intelligenti nell'utilizzare in modo efficiente le connessioni al database e dovrebbero essere in grado di scalare senza sforzo. - Sicurezza
Ne consegue dall'alto che per autorizzare l'accesso ai dati è necessario un sistema di controllo degli accessi basato sui ruoli maturo. - Automazione
Se non conosci l'ecosistema GraphQL, scrivere a mano uno schema GraphQL e implementare un server GraphQL può sembrare un compito arduo. Massimizza l'automazione dei tuoi strumenti in modo da poterti concentrare su cose importanti come la creazione di funzionalità front-end incentrate sull'utente. - Architettura Per quanto banali sembrino gli sforzi di cui sopra, l'architettura di back-end di un'app di livello produttivo può coinvolgere concetti GraphQL avanzati come lo schema-stitching, ecc. Inoltre, la capacità di generare/consumare facilmente API in tempo reale apre la possibilità di costruire asincrone, reattivo app resilienti e intrinsecamente scalabili. Pertanto, è fondamentale valutare in che modo gli strumenti GraphQL possono semplificare la tua architettura.
Risorse correlate
- Puoi controllare una versione live dell'app qui.
- Il codice sorgente completo è disponibile su GitHub.
- Se desideri esplorare lo schema del database ed eseguire test di query GraphQL, puoi farlo qui.