Come progettare un'applicazione Web basata su WebSocket in tempo reale?


17

Nel processo di sviluppo di un'applicazione a pagina singola in tempo reale, ho progressivamente adottato i websocket per fornire ai miei utenti dati aggiornati. Durante questa fase, ero triste nel notare che stavo distruggendo troppo la mia struttura di app e non sono riuscito a trovare una soluzione a questo fenomeno.

Prima di entrare nei dettagli, solo un po 'di contesto:

  • La webapp è una SPA in tempo reale;
  • Il backend è in Ruby on Rails. Ruby invia gli eventi in tempo reale a una chiave Redis, quindi un server di micro nodi lo ritira e lo invia a Socket.Io;
  • Il frontend è in AngularJS e si collega direttamente al server socket.io in Node.

Sul lato server, prima in tempo reale avevo una chiara separazione delle risorse basata su controller / modello, con l'elaborazione allegata a ciascuna. Questo classico design MVC è stato completamente distrutto, o almeno bypassato, proprio quando ho iniziato a spingere roba tramite websocket ai miei utenti. Ora ho una singola pipe in cui tutta la mia app scorre in modo più o meno strutturato . E lo trovo stressante.

Sul fronte, la preoccupazione principale è la duplicazione della logica aziendale. Quando l'utente carica la pagina, devo caricare i miei modelli attraverso le classiche chiamate AJAX. Ma devo anche gestire il flooding dei dati in tempo reale e mi ritrovo a duplicare gran parte della mia logica di business lato client per mantenere la coerenza dei miei modelli lato client.

Dopo alcune ricerche, non riesco a trovare buoni post, articoli, libri o qualsiasi altra cosa che possa dare consigli su come si può e si dovrebbe progettare l'architettura di una webapp moderna con alcuni argomenti specifici in mente:

  • Come strutturare i dati inviati dal server all'utente?
    • Devo solo inviare eventi come "questa risorsa è stata aggiornata e è necessario ricaricarla tramite una chiamata AJAX" o inviare i dati aggiornati e sostituire i dati precedenti caricati tramite le chiamate AJAX iniziali?
    • Come definire uno scheletro coerente e scalabile per i dati inviati? è questo un messaggio di aggiornamento del modello o "si è verificato un errore con blahblahblah"
  • Come non inviare dati su tutto da qualsiasi parte del back-end?
  • Come ridurre la duplicazione della logica aziendale sia sul lato server che sul lato client?

Sei sicuro che Rails sia l'opzione migliore per la tua SPA? Rails è fantastico, ma è rivolto all'applicazione monolite ... potresti voler un backend modulare con separazione delle preoccupazioni ... A seconda delle tue esigenze, prenderei in considerazione framework Ruby alternativi per una SPA in tempo reale.
Myst,

1
Non sono sicuro che Rails sia l'opzione migliore, ma sono molto soddisfatto dello stack in atto almeno sul back-end (probabilmente perché sono bravo con questo particolare framework). La mia preoccupazione qui è più su come progettare la SPA da un punto di vista tecnico agnostico. Inoltre, non voglio moltiplicare il numero di lingue, framework e librerie in un singolo progetto.
Philippe Durix,

Il benchmark collegato potrebbe presentare problemi, ma espone una debolezza nell'attuale implementazione di ActiveRecord. Potrei essere di parte su plezi.io , ma come sottolineato nei risultati successivi del benchmark , offre miglioramenti significativi delle prestazioni, anche prima del clustering e di Redis (che non sono stati testati). Penso che abbia funzionato meglio di node.js ... Fino a quando le cose non cambieranno, userei plezi.io.
Myst,

Risposte:


10

Come strutturare i dati inviati dal server all'utente?

Usa il modello di messaggistica . Bene, stai già utilizzando un protocollo di messaggistica, ma intendo strutturare le modifiche come messaggi ... in particolare eventi. Quando il lato server cambia, ciò si traduce in eventi aziendali. Nel tuo scenario, le visualizzazioni dei tuoi clienti sono interessate a questi eventi. Gli eventi dovrebbero contenere tutti i dati rilevanti per quella modifica (non necessariamente tutti i dati di visualizzazione). La pagina client dovrebbe quindi aggiornare le parti della vista che mantiene con i dati dell'evento.

Ad esempio, se stavi aggiornando un ticker azionario e AAPL fosse cambiato, non vorrai abbassare tutti i prezzi delle azioni o anche tutti i dati su AAPL (nome, descrizione, ecc.). Spingeresti solo AAPL, il delta e il nuovo prezzo. Sul client, si aggiornerà quindi solo quel prezzo azionario nella vista.

Devo solo inviare eventi come "questa risorsa è stata aggiornata e è necessario ricaricarla tramite una chiamata AJAX" o inviare i dati aggiornati e sostituire i dati precedenti caricati tramite le chiamate AJAX iniziali?

Non direi nessuno dei due. Se stai inviando l'evento, vai avanti e invia con te i dati pertinenti (non i dati dell'intero oggetto). Dagli un nome per il tipo di evento che è. (La denominazione e quali dati sono rilevanti per quell'evento va oltre lo scopo delle lavorazioni meccaniche del sistema. Ciò ha più a che fare con il modo in cui viene modellata la logica aziendale.) I programmi di aggiornamento della vista devono sapere come tradurre ogni evento specifico in una modifica precisa della vista (ovvero aggiorna solo ciò che è cambiato).

Come definire uno scheletro coerente e scalabile per i dati inviati? è questo un messaggio di aggiornamento del modello o "si è verificato un errore con blahblahblah"

Direi che questa è una grande domanda a risposta aperta che dovrebbe essere suddivisa in molte altre domande e pubblicata separatamente.

In generale, tuttavia, il sistema di back-end dovrebbe creare e inviare eventi per eventi importanti per l'azienda. Questi potrebbero provenire da feed esterni o da attività nel back-end stesso.

Come non inviare dati su tutto da qualsiasi parte del back-end?

Usa il modello di pubblicazione / iscrizione . Quando la SPA carica una nuova pagina che è interessata a ricevere aggiornamenti in tempo reale, la pagina dovrebbe iscriversi solo agli eventi che può utilizzare e chiamare la logica di aggiornamento della vista man mano che tali eventi entrano. Probabilmente avrai bisogno della logica pub / sub il server per ridurre il carico di rete. Esistono librerie per Websocket pub / sub, ma non sono sicuro di cosa siano nell'ecosistema Rails.

Come ridurre la duplicazione della logica aziendale sia sul lato server che sul lato client?

Sembra che tu debba aggiornare i dati di visualizzazione sia sul client che sul server. Suppongo che siano necessari i dati di visualizzazione sul lato server in modo da disporre di un'istantanea per avviare il client in tempo reale. Dato che ci sono due lingue / piattaforme coinvolte (Ruby e Javascript), la logica di aggiornamento della vista dovrà essere scritta in entrambe. A parte la traspilazione (che ha i suoi problemi), non vedo un modo per aggirare questo.

Punto tecnico: la manipolazione dei dati (visualizza aggiornamento) non è una logica aziendale. Se si intende la convalida del caso d'uso, ciò sembra inevitabile poiché le convalide del client sono necessarie per una buona esperienza utente, ma alla fine non possono essere ritenute affidabili dal server.


Ecco come vedo una cosa del genere strutturata bene.

Visualizzazioni client:

  • Richiede un'istantanea della vista e il numero dell'ultimo evento visto della vista
    • Questo prepopolerà la vista in modo che il client non debba costruire da zero.
    • Potrebbe essere su HTTP GET per semplicità
  • Stabilisce una connessione al websocket e si iscrive a eventi specifici, a partire dall'ultimo numero dell'evento della vista.
  • Riceve eventi tramite websocket e aggiorna la sua vista in base al tipo / ai dati dell'evento.

Comandi del cliente:

  • Richiedi modifica dati (HTTP PUT / POST / DELETE)
    • La risposta è solo successo o fallimento + errore
    • (Gli eventi generati dalla modifica verranno inseriti nel websocket e genereranno un aggiornamento della vista.)

Il lato server potrebbe effettivamente essere suddiviso in più componenti con responsabilità limitate. Uno che elabora solo le richieste in arrivo e crea eventi. Un altro potrebbe gestire gli abbonamenti dei clienti, ascoltare gli eventi (diciamo in-process) e inoltrare gli eventi appropriati agli abbonati. Potresti avere un terzo che ascolta gli eventi e aggiorna le visualizzazioni lato server, forse ciò accade anche prima che gli abbonati ricevano gli eventi.

Quello che ho descritto è una forma di CQRS + Messaggi e una strategia tipica per affrontare il tipo di problemi che stai affrontando.

Non ho inserito Event Sourcing in questa descrizione in quanto non sono sicuro se si tratti di qualcosa che si desidera affrontare o se è necessario. Ma è un modello correlato.


Ho progredito molto sull'argomento e i suggerimenti che hai dato sono stati molto utili. Ho accettato la risposta perché ho usato molti dei consigli, anche se non li ho usati tutti. Descriverò il percorso che ho seguito in un'altra risposta.
Philippe Durix,

4

Dopo alcuni mesi di lavoro sul backend principalmente, sono stato in grado di utilizzare alcuni dei consigli qui per affrontare i problemi che la piattaforma stava affrontando.

L'obiettivo principale nel ripensare il backend era attenersi il più possibile al CRUD. Tutte le azioni, i messaggi e le richieste sparsi su molti percorsi sono stati raggruppati in risorse che sono state create, aggiornate, lette o eliminate . Sembra ovvio ora, ma questo è stato un modo molto difficile di pensare di applicare con attenzione.

Dopo che tutto è stato organizzato in risorse, sono stato in grado di allegare messaggi in tempo reale ai modelli.

  • La creazione innesca un messaggio con una nuova risorsa buca;
  • Aggiornamento attiva un messaggio con solo gli attributi aggiornati (più l'UUID);
  • La cancellazione attiva un messaggio di cancellazione.

Sull'API Rest, tutti i metodi di creazione, aggiornamento ed eliminazione generano una risposta unica, il codice HTTP che informa dell'esito positivo o negativo e i dati effettivi trasferiti sui socket Web.

Sul front-end, ogni risorsa viene gestita da un componente specifico che li carica tramite HTTP all'inizializzazione, quindi sottoscrive gli aggiornamenti e mantiene il loro stato nel tempo. Le viste quindi si legano a questi componenti per visualizzare risorse ed eseguire azioni su tali risorse attraverso gli stessi componenti.


Ho trovato interessanti le letture di CQRS + Messaging ed Event Sourcing, ma mi è sembrato un po 'troppo complicato per il mio problema e forse è più adatto alle applicazioni intensive in cui il commit dei dati in un database centralizzato è un lusso costoso. Ma terrò sicuramente a mente questo approccio.

In questo caso, l'app avrà pochi client simultanei e ho preso la parte di fare molto affidamento sul database. I modelli più mutevoli sono archiviati in Redis, di cui mi fido a gestire alcune centinaia di aggiornamenti al secondo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.