it-swarm.com.de

Wie kann eine auf Echtzeit-Websites basierende Webanwendung erstellt werden?

Bei der Entwicklung einer Echtzeit-Einzelseitenanwendung habe ich schrittweise Websockets eingeführt, um meine Benutzer mit aktuellen Daten zu versorgen. Während dieser Phase war ich traurig zu bemerken, dass ich viel zu viel von meiner App-Struktur zerstört habe, und ich konnte keine Lösung für dieses Phänomen finden.

Bevor wir auf Einzelheiten eingehen, nur ein wenig Kontext:

  • Die Webapp ist ein Echtzeit-SPA.
  • Das Backend befindet sich in Ruby on Rails. Echtzeitereignisse werden von Ruby auf einen Redis-Schlüssel übertragen, dann zieht ein Mikroknotenserver diesen zurück und sendet ihn an Socket.Io;
  • Das Frontend befindet sich in AngularJS und stellt eine direkte Verbindung zum socket.io-Server in Node her.

Auf der Serverseite hatte ich vor Echtzeit eine klare Controller/Modell-basierte Trennung der Ressourcen, an die jeweils eine Verarbeitung angehängt war. Dieses klassisches MVC-Design wurde vollständig verkleinert oder zumindest umgangen, als ich anfing, Inhalte über Websockets an meine Benutzer zu senden. Ich habe jetzt eine einzelne Pipe, in der meine gesamte App mehr oder weniger strukturierte Daten abfließt. Und ich finde es stressig.

Im Frontend ist das Hauptanliegen die Verdoppelung der Geschäftslogik. Wenn der Benutzer die Seite lädt, muss ich meine Modelle über klassische AJAX Aufrufe laden. Ich muss mich aber auch mit dem Überfluten von Echtzeitdaten befassen und finde mich dabei, einen Großteil meiner clientseitigen Geschäftslogik zu duplizieren, um die Konsistenz meiner clientseitigen Modelle zu gewährleisten.

Nach einigen Recherchen kann ich keine guten Beiträge, Artikel, Bücher oder was auch immer finden, die Hinweise geben, wie man die Architektur einer modernen Webanwendung unter Berücksichtigung einiger spezifischer Themen gestalten kann und sollte:

  • Wie strukturiere ich die Daten, die vom Server an den Benutzer gesendet werden?
    • Sollte ich nur Ereignisse wie "Diese Ressource wurde aktualisiert und Sie sollten sie über einen AJAX -Aufruf neu laden" senden oder die aktualisierten Daten pushen und vorherige Daten ersetzen, die über erste AJAX Aufrufe geladen wurden?
    • Wie definiere ich ein kohärentes und skalierbares Skelett für gesendete Daten? Ist dies eine Modellaktualisierungsnachricht oder die Meldung "Es ist ein Fehler mit blahblahblah aufgetreten"
  • Wie kann man nicht von überall im Backend Daten über alles senden?
  • Wie kann die Duplizierung der Geschäftslogik sowohl auf der Server- als auch auf der Clientseite reduziert werden?
17
Philippe Durix

Wie strukturiere ich die Daten, die vom Server an den Benutzer gesendet werden?

Verwenden Sie das Messaging-Muster . Nun, Sie verwenden bereits ein Messaging-Protokoll, aber ich meine, strukturieren Sie die Änderungen als Nachrichten ... speziell als Ereignisse. Wenn sich die Serverseite ändert, führt dies zu Geschäftsereignissen. In Ihrem Szenario sind Ihre Kundenansichten an diesen Ereignissen interessiert. Die Ereignisse sollten alle für diese Änderung relevanten Daten enthalten (nicht unbedingt alle Ansichtsdaten). Die Client-Seite sollte dann die Teile der Ansicht, die sie verwaltet, mit den Ereignisdaten aktualisieren.

Wenn Sie beispielsweise einen Börsenticker aktualisieren und AAPL ändern, möchten Sie nicht alle Aktienkurse oder sogar alle Daten zu AAPL (Name, Beschreibung usw.) nach unten drücken. Sie würden nur AAPL, das Delta und den neuen Preis drücken. Auf dem Client würden Sie dann nur diesen Aktienkurs in der Ansicht aktualisieren.

Sollte ich nur Ereignisse wie "Diese Ressource wurde aktualisiert und Sie sollten sie über einen AJAX -Aufruf neu laden" senden oder die aktualisierten Daten pushen und vorherige Daten ersetzen, die über erste AJAX Aufrufe geladen wurden?

Ich würde auch nicht sagen. Wenn Sie das Ereignis senden, senden Sie relevante Daten (nicht die Daten des gesamten Objekts). Geben Sie ihm einen Namen für die Art des Ereignisses. (Die Benennung und die für dieses Ereignis relevanten Daten liegen außerhalb des Bereichs der mechanischen Funktionsweise des Systems. Dies hat mehr mit der Modellierung der Geschäftslogik zu tun.) Ihre Ansichtsaktualisierer müssen wissen, wie jedes bestimmte Ereignis übersetzt werden kann eine genaue Ansichtsänderung (dh nur aktualisieren, was geändert wurde).

Wie definiere ich ein kohärentes und skalierbares Skelett für gesendete Daten? Ist dies eine Modellaktualisierungsnachricht oder die Meldung "Es ist ein Fehler mit blahblahblah aufgetreten"

Ich würde sagen, dies ist eine große, offene Frage, die in mehrere andere Fragen unterteilt und separat gestellt werden sollte.

Im Allgemeinen sollte Ihr Back-End-System jedoch Ereignisse für wichtige Ereignisse in Ihrem Unternehmen erstellen und versenden. Diese können von externen Feeds oder von Aktivitäten im Back-End selbst stammen.

Wie kann man nicht von überall im Backend Daten über alles senden?

Verwenden Sie das Publish/Subscribe-Muster . Wenn Ihr SPA eine neue Seite lädt, die an Aktualisierungen in Echtzeit interessiert ist, sollte die Seite nur die Ereignisse abonnieren, die sie verwenden kann, und die Ansichtsaktualisierungslogik aufrufen, sobald diese Ereignisse eingehen. Möglicherweise benötigen Sie Pub/Sub-Logik der Server, um die Netzwerklast zu reduzieren. Für Websocket Pub/Sub gibt es Bibliotheken, aber ich bin mir nicht sicher, welche sich im Rails Ökosystem befinden.

Wie kann die Duplizierung der Geschäftslogik sowohl auf der Server- als auch auf der Clientseite reduziert werden?

Es hört sich so an, als müssten Sie die Ansichtsdaten sowohl auf dem Client als auch auf dem Server aktualisieren. Ich vermute, Sie benötigen die serverseitigen Ansichtsdaten, damit Sie einen Snapshot haben, um den Echtzeit-Client zu starten. Da zwei Sprachen/Plattformen beteiligt sind (Ruby und Javascript), muss die Ansichtsaktualisierungslogik in beiden Sprachen geschrieben werden. Abgesehen von der Transpilierung (die ihre eigenen Probleme hat) sehe ich keinen Weg daran vorbei.

Technischer Punkt: Datenmanipulation (View Update) ist keine Geschäftslogik. Wenn Sie die Validierung von Anwendungsfällen meinen, scheint dies unvermeidlich, da die Validierungen des Clients für eine gute Benutzererfahrung erforderlich sind, aber letztendlich vom Server nicht als vertrauenswürdig eingestuft werden können.


So sehe ich so etwas gut strukturiert.

Client-Ansichten:

  • Fordert einen Ansichtsschnappschuss und die zuletzt gesehene Ereignisnummer der Ansicht an
    • Dadurch wird die Ansicht vorab ausgefüllt, sodass der Client nicht von Grund auf neu erstellen muss.
    • Könnte der Einfachheit halber über HTTP GET sein
  • Stellt eine Websocket-Verbindung her und abonniert bestimmte Ereignisse, beginnend mit der letzten Ereignisnummer der Ansicht.
  • Empfängt Ereignisse über den Websocket und aktualisiert seine Ansicht basierend auf Ereignistyp/Daten.

Client-Befehle:

  • Datenänderung anfordern (HTTP PUT/POST/DELETE)
    • Antwort ist nur Erfolg oder Misserfolg + Fehler
    • (Die durch die Änderung generierten Ereignisse werden über den Websocket übertragen und lösen eine Ansichtsaktualisierung aus.)

Die Serverseite könnte tatsächlich in mehrere Komponenten mit begrenzten Verantwortlichkeiten aufgeteilt werden. Eine, die nur die eingehenden Anforderungen verarbeitet und Ereignisse erstellt. Ein anderer könnte Client-Abonnements verwalten, auf Ereignisse warten (z. B. in Bearbeitung) und entsprechende Ereignisse an Abonnenten weiterleiten. Sie könnten ein Drittel haben, das auf Ereignisse wartet und serverseitige Ansichten aktualisiert - möglicherweise geschieht dies sogar, bevor Abonnenten die Ereignisse erhalten.

Was ich beschrieben habe, ist eine Form von [~ # ~] cqrs [~ # ~] + Messaging und eine typische Strategie zur Adressierung der Art von Probleme, mit denen Sie konfrontiert sind.

Ich habe Event Sourcing nicht in diese Beschreibung aufgenommen, da ich nicht sicher bin, ob es etwas ist, das Sie übernehmen möchten oder ob Sie es unbedingt benötigen. Aber es ist ein verwandtes Muster.

10
Kasey Speakman

Nach einigen Monaten Arbeit hauptsächlich am Backend konnte ich einige der Ratschläge hier verwenden, um die Probleme zu lösen, mit denen die Plattform konfrontiert war.

Das Hauptziel beim Überdenken des Backends war es, so hart wie möglich an CRUD festzuhalten. Alle Aktionen, Nachrichten und Anforderungen, die auf vielen Routen verteilt sind, wurden in Ressourcen zusammengefasst, die erstellt, aktualisiert, gelesen oder gelöscht werden.. Es klingt jetzt offensichtlich, aber dies war eine sehr schwierige Denkweise, um sie sorgfältig anzuwenden.

Nachdem alles in Ressourcen organisiert wurde, konnte ich Echtzeitnachrichten an Modelle anhängen.

  • Die Erstellung löst eine Nachricht mit einer neuen Ressource aus.
  • Update löst eine Nachricht nur mit den aktualisierten Attributen (plus der UUID) aus.
  • Das Löschen löst eine Löschnachricht aus.

In der Rest-API generieren alle Methoden zum Erstellen, Aktualisieren und Löschen eine Nur-Kopf-Antwort. Der HTTP-Code informiert über Erfolg oder Misserfolg und die tatsächlichen Daten, die über Websockets übertragen werden.

Im Front-End werden alle Ressourcen von einer bestimmten Komponente verwaltet, die sie bei der Initialisierung über HTTP lädt, dann Updates abonniert und ihren Status über einen längeren Zeitraum beibehält. Ansichten werden dann an diese Komponenten gebunden, um Ressourcen anzuzeigen und Aktionen für diese Ressourcen über dieselben Komponenten auszuführen.


Ich fand das Lesen von CQRS + Messaging und Event Sourcing sehr interessant, fand es jedoch etwas überkompliziert für mein Problem und ist möglicherweise eher für intensive Anwendungen geeignet, bei denen das Festschreiben von Daten in eine zentralisierte Datenbank ein teurer Luxus ist. Aber ich werde diesen Ansatz auf jeden Fall berücksichtigen.

In diesem Fall hat die App nur wenige gleichzeitige Clients, und ich habe mich viel auf die Datenbank verlassen. Die sich am meisten ändernden Modelle werden in Redis gespeichert, von dem ich vertraue, dass sie einige hundert Updates pro Sekunde verarbeiten.

4
Philippe Durix