Habr-Frontend-Entwicklerprotokolle: Refactoring und Reflektion

Habr-Frontend-Entwicklerprotokolle: Refactoring und Reflektion

Mich hat schon immer interessiert, wie Habr von innen aufgebaut ist, wie der Workflow strukturiert ist, wie die Kommunikation strukturiert ist, welche Standards verwendet werden und wie hier generell Code geschrieben wird. Glücklicherweise bekam ich eine solche Gelegenheit, denn ich bin seit kurzem Teil des Habra-Teams. Am Beispiel eines kleinen Refactorings der mobilen Version versuche ich die Frage zu beantworten: Wie ist es, hier an der Front zu arbeiten? Im Programm: Node, Vue, Vuex und SSR mit Soße aus Notizen zu persönlichen Erfahrungen in Habr.

Das erste, was Sie über das Entwicklungsteam wissen müssen, ist, dass es nur wenige von uns gibt. Nicht genug – das sind drei Fronten, zwei Backs und der technische Vorsprung aller Habr – Baxley. Natürlich gibt es auch einen Tester, einen Designer, drei Vadim, einen Wunderbesen, einen Marketingspezialisten und andere Bumburums. Aber es gibt nur sechs direkte Mitwirkende an Habrs Quellen. Das ist ziemlich selten – ein Projekt mit einem Multimillionen-Dollar-Publikum, das von außen wie ein riesiges Unternehmen aussieht, sieht in Wirklichkeit eher wie ein gemütliches Startup mit einer möglichst flachen Organisationsstruktur aus.

Wie viele andere IT-Unternehmen bekennt sich Habr zu agilen Ideen, CI-Praktiken und das ist alles. Aber nach meinem Gefühl entwickelt sich Habr als Produkt eher in Wellen als kontinuierlich. Also programmieren wir mehrere Sprints hintereinander fleißig etwas, designen und redesignen, machen etwas kaputt und reparieren es, lösen Tickets auf und erstellen neue, treten auf einen Rechen und schießen uns selbst in die Füße, um das Feature schließlich freizugeben Produktion. Und dann kommt eine gewisse Flaute, eine Zeit der Neuentwicklung, Zeit, das zu tun, was im Quadranten „wichtig – nicht dringend“ liegt.

Genau auf diesen „Off-Season“-Sprint soll im Folgenden eingegangen werden. Diesmal beinhaltete es eine Umgestaltung der mobilen Version von Habr. Generell setzt das Unternehmen große Hoffnungen darauf und soll in Zukunft den gesamten Zoo der Habr-Inkarnationen ersetzen und zu einer universellen plattformübergreifenden Lösung werden. Eines Tages wird es adaptives Layout, PWA, Offline-Modus, Benutzeranpassung und viele andere interessante Dinge geben.

Stellen wir die Aufgabe

Einmal sprach einer der Frontleute bei einem gewöhnlichen Stand-up über Probleme in der Architektur der Kommentarkomponente der mobilen Version. Vor diesem Hintergrund haben wir ein Mikrotreffen im Format einer Gruppenpsychotherapie organisiert. Alle sagten abwechselnd, wo es weh tat, sie hielten alles auf Papier, sie hatten Mitgefühl, sie verstanden, außer dass niemand klatschte. Das Ergebnis war eine Liste mit 20 Problemen, die deutlich machte, dass der mobile Habr noch einen langen und steinigen Weg zum Erfolg vor sich hatte.

Mir ging es vor allem um Ressourceneffizienz und eine sogenannte reibungslose Schnittstelle. Jeden Tag sah ich auf der Heim-Arbeit-Heim-Route, wie mein altes Telefon verzweifelt versuchte, 20 Schlagzeilen im Feed anzuzeigen. Es sah ungefähr so ​​aus:

Habr-Frontend-Entwicklerprotokolle: Refactoring und ReflektionMobile Habr-Schnittstelle vor dem Refactoring

Was ist denn hier los? Kurz gesagt: Der Server stellte die HTML-Seite jedem auf die gleiche Weise zur Verfügung, unabhängig davon, ob der Benutzer angemeldet war oder nicht. Dann wird der Client JS geladen und fordert erneut die notwendigen Daten an, jedoch angepasst zur Autorisierung. Das heißt, wir haben tatsächlich zweimal die gleiche Arbeit gemacht. Die Benutzeroberfläche flackerte und der Benutzer lud gut hundert zusätzliche Kilobyte herunter. Im Detail sah alles noch gruseliger aus.

Habr-Frontend-Entwicklerprotokolle: Refactoring und ReflektionAltes SSR-CSR-Schema. Die Autorisierung ist nur in den Stufen C3 und C4 möglich, wenn Node JS nicht damit beschäftigt ist, HTML zu generieren und Anfragen an die API weiterleiten kann.

Unsere damalige Architektur wurde von einem der Habr-Benutzer sehr genau beschrieben:

Die mobile Version ist Mist. Ich sage es so, wie es ist. Eine schreckliche Kombination aus SSR und CSR.

Wir mussten es zugeben, egal wie traurig es war.

Ich habe die Optionen bewertet, in Jira ein Ticket mit einer Beschreibung auf der Ebene „Jetzt ist es schlecht, machen Sie es richtig“ erstellt und die Aufgabe in grobe Züge zerlegt:

  • Daten wiederverwenden,
  • Minimieren Sie die Anzahl der Neuzeichnungen,
  • Eliminieren Sie doppelte Anfragen,
  • Machen Sie den Ladevorgang deutlicher.

Lassen Sie uns die Daten wiederverwenden

Theoretisch ist das serverseitige Rendering darauf ausgelegt, zwei Probleme zu lösen: nicht unter Suchmaschineneinschränkungen zu leiden SPA-Indizierung und die Metrik verbessern FMP (unweigerlich schlimmer TTI). In einem klassischen Szenario, das endlich 2013 bei Airbnb formuliert Jahr (noch auf Backbone.js) ist SSR dieselbe isomorphe JS-Anwendung, die in der Node-Umgebung ausgeführt wird. Der Server sendet lediglich das generierte Layout als Antwort auf die Anfrage. Dann erfolgt die Rehydrierung auf der Clientseite und dann funktioniert alles ohne Neuladen der Seite. Für Habr, wie auch für viele andere Ressourcen mit Textinhalten, ist das Server-Rendering ein entscheidendes Element beim Aufbau freundschaftlicher Beziehungen zu Suchmaschinen.

Trotz der Tatsache, dass seit dem Aufkommen der Technologie mehr als sechs Jahre vergangen sind und in dieser Zeit in der Front-End-Welt wirklich viel Wasser unter die Brücke geflossen ist, ist diese Idee für viele Entwickler immer noch von Geheimnissen umgeben. Wir blieben nicht stehen und führten eine Vue-Anwendung mit SSR-Unterstützung in die Produktion ein, wobei wir ein kleines Detail übersahen: Wir haben den Ausgangszustand nicht an den Kunden gesendet.

Warum? Auf diese Frage gibt es keine genaue Antwort. Entweder wollten sie die Größe der Antwort vom Server nicht erhöhen, oder es gab eine Reihe anderer Architekturprobleme, oder es kam einfach nicht zum Erfolg. Auf die eine oder andere Weise erscheint es durchaus angemessen und nützlich, den Status zu verwerfen und alles wiederzuverwenden, was der Server getan hat. Die Aufgabe ist eigentlich trivial - Der Staat wird einfach injiziert in den Ausführungskontext und Vue fügt es automatisch als globale Variable zum generierten Layout hinzu: window.__INITIAL_STATE__.

Eines der aufgetretenen Probleme ist die Unfähigkeit, zyklische Strukturen in JSON zu konvertieren (zirkulärer Verweis); wurde gelöst, indem solche Strukturen einfach durch ihre flachen Gegenstücke ersetzt wurden.

Darüber hinaus sollten Sie beim Umgang mit UGC-Inhalten bedenken, dass die Daten in HTML-Entitäten konvertiert werden sollten, um den HTML-Code nicht zu beschädigen. Für diese Zwecke verwenden wir he.

Neuzeichnungen minimieren

Wie Sie dem obigen Diagramm entnehmen können, führt in unserem Fall eine Node-JS-Instanz zwei Funktionen aus: SSR und „Proxy“ in der API, wo die Benutzerautorisierung erfolgt. Dieser Umstand macht eine Autorisierung unmöglich, während der JS-Code auf dem Server ausgeführt wird, da der Knoten Single-Threaded ist und die SSR-Funktion synchron ist. Das heißt, der Server kann einfach keine Anfragen an sich selbst senden, während der Callstack mit etwas beschäftigt ist. Es stellte sich heraus, dass wir den Status aktualisiert hatten, die Schnittstelle jedoch nicht aufhörte zu zucken, da die Daten auf dem Client unter Berücksichtigung der Benutzersitzung aktualisiert werden mussten. Wir mussten unserer Anwendung beibringen, unter Berücksichtigung der Benutzeranmeldung die richtigen Daten in den Ausgangszustand zu versetzen.

Es gab nur zwei Lösungen für das Problem:

  • Autorisierungsdaten an serverübergreifende Anfragen anhängen;
  • Teilen Sie Node JS-Ebenen in zwei separate Instanzen auf.

Die erste Lösung erforderte die Verwendung globaler Variablen auf dem Server und die zweite verlängerte die Frist für die Erledigung der Aufgabe um mindestens einen Monat.

Wie trifft man eine Wahl? Habr geht oft den Weg des geringsten Widerstands. Generell besteht der Wunsch, den Zyklus von der Idee bis zum Prototyp auf ein Minimum zu reduzieren. Das Modell der Einstellung zum Produkt erinnert ein wenig an die Postulate von booking.com, mit dem einzigen Unterschied, dass Habr das Feedback der Nutzer viel ernster nimmt und Ihnen als Entwickler vertraut, solche Entscheidungen zu treffen.

Dieser Logik und meinem eigenen Wunsch folgend, das Problem schnell zu lösen, habe ich mich für globale Variablen entschieden. Und wie so oft muss man früher oder später dafür bezahlen. Wir haben fast sofort bezahlt: Wir haben am Wochenende gearbeitet, die Konsequenzen geklärt, geschrieben postmortale und begann, den Server in zwei Teile zu teilen. Der Fehler war sehr dumm und der damit verbundene Fehler war nicht einfach zu reproduzieren. Und ja, das ist schade, aber auf die eine oder andere Weise ist mein PoC mit globalen Variablen stolpernd und ächzend dennoch in Produktion gegangen und arbeitet recht erfolgreich, während er auf den Wechsel zu einer neuen „Zwei-Knoten“-Architektur wartet. Dies war ein wichtiger Schritt, denn formal wurde das Ziel erreicht – SSR lernte, eine vollständig gebrauchsfertige Seite bereitzustellen, und die Benutzeroberfläche wurde viel ruhiger.

Habr-Frontend-Entwicklerprotokolle: Refactoring und ReflektionMobile Habr-Schnittstelle nach der ersten Phase des Refactorings

Letztendlich führt die SSR-CSR-Architektur der mobilen Version zu diesem Bild:

Habr-Frontend-Entwicklerprotokolle: Refactoring und Reflektion„Zwei-Knoten“-SSR-CSR-Schaltung. Die Node JS API ist immer für asynchrone I/O bereit und wird nicht durch die SSR-Funktion blockiert, da sich diese in einer separaten Instanz befindet. Abfragekette Nr. 3 wird nicht benötigt.

Eliminierung doppelter Anfragen

Nachdem die Manipulationen durchgeführt wurden, löste die anfängliche Darstellung der Seite keine Epilepsie mehr aus. Doch die weitere Nutzung von Habr im SPA-Modus sorgte dennoch für Verwirrung.

Da die Grundlage des Benutzerflusses Übergänge des Formulars sind Liste der Artikel → Artikel → Kommentare und umgekehrt war es wichtig, den Ressourcenverbrauch dieser Kette überhaupt zu optimieren.

Habr-Frontend-Entwicklerprotokolle: Refactoring und ReflektionDie Rückkehr zum Beitrags-Feed führt zu einer neuen Datenanforderung

Es war nicht nötig, tief zu graben. Im Screencast oben können Sie sehen, dass die Anwendung beim Zurückwischen erneut die Liste der Artikel anfordert und wir während der Anfrage die Artikel nicht sehen, was bedeutet, dass die vorherigen Daten irgendwo verschwinden. Es sieht so aus, als ob die Artikellistenkomponente einen lokalen Status verwendet und ihn beim Zerstören verliert. Tatsächlich verwendete die Anwendung einen globalen Status, aber die Vuex-Architektur wurde direkt aufgebaut: Module sind an Seiten gebunden, die wiederum an Routen gebunden sind. Darüber hinaus sind alle Module „wegwerfbar“ – bei jedem weiteren Besuch der Seite wurde das gesamte Modul neu geschrieben:

ArticlesList: [
  { Article1 },
  ...
],
PageArticle: { ArticleFull1 },

Insgesamt hatten wir ein Modul Artikelliste, das Objekte vom Typ enthält Artikel und Modul Seitenartikel, was eine erweiterte Version des Objekts war Artikel, So'ne Art Artikelvollständig. Im Großen und Ganzen hat diese Implementierung nichts Schlimmes an sich – sie ist sehr einfach, man könnte sogar sagen naiv, aber äußerst verständlich. Wenn Sie das Modul bei jeder Routenänderung zurücksetzen, können Sie sogar damit leben. Allerdings können Sie beispielsweise zwischen Artikel-Feeds wechseln /feed → /all, wirft garantiert alles weg, was mit dem persönlichen Feed zu tun hat, da wir nur einen haben Artikelliste, in die Sie neue Daten eingeben müssen. Dies führt wiederum zu einer Duplizierung von Anfragen.

Nachdem ich alles gesammelt hatte, was ich zu diesem Thema herausfinden konnte, formulierte ich eine neue Staatsstruktur und präsentierte sie meinen Kollegen. Die Diskussionen waren langwierig, aber am Ende überwogen die Argumente dafür die Zweifel und ich begann mit der Umsetzung.

Die Logik einer Lösung lässt sich am besten in zwei Schritten offenbaren. Zuerst versuchen wir, das Vuex-Modul von Seiten zu entkoppeln und direkt an Routen zu binden. Ja, es wird etwas mehr Daten im Store geben, Getter werden etwas komplexer, aber wir werden Artikel nicht doppelt laden. Für die mobile Version ist dies vielleicht das stärkste Argument. Es wird ungefähr so ​​aussehen:

ArticlesList: {
  ROUTE_FEED: [ 
    { Article1 },
    ...
  ],
  ROUTE_ALL: [ 
    { Article2 },
    ...
  ],
}

Was aber, wenn sich Artikellisten zwischen mehreren Routen überschneiden können und wir Objektdaten wiederverwenden möchten? Artikel um die Beitragsseite zu rendern und sie in umzuwandeln Artikelvollständig? In diesem Fall wäre es logischer, eine solche Struktur zu verwenden:

ArticlesIds: {
  ROUTE_FEED: [ '1', ... ],
  ROUTE_ALL: [ '1', '2', ... ],
},
ArticlesList: {
  '1': { Article1 }, 
  '2': { Article2 },
  ...
}

Artikelliste hier ist es nur eine Art Artikel-Repository. Alle Artikel, die während der Benutzersitzung heruntergeladen wurden. Wir behandeln sie mit größter Sorgfalt, da es sich hierbei um Datenverkehr handelt, der möglicherweise durch Schmerzen irgendwo in der U-Bahn zwischen den Stationen heruntergeladen wurde, und wir möchten dem Benutzer auf keinen Fall noch einmal diese Schmerzen bereiten, indem wir ihn zwingen, Daten zu laden, über die er bereits verfügt heruntergeladen. Ein Objekt Artikel-IDs ist einfach ein Array von IDs (als ob „Links“) zu Objekten Artikel. Mit dieser Struktur können Sie die Duplizierung gemeinsamer Daten für Routen und die Wiederverwendung des Objekts vermeiden Artikel beim Rendern einer Beitragsseite durch Zusammenführen erweiterter Daten.

Auch die Ausgabe der Artikelliste ist transparenter geworden: Die Iterator-Komponente durchläuft das Array mit Artikel-IDs und zeichnet die Artikel-Teaser-Komponente, indem sie die Id als Requisite übergibt, und die untergeordnete Komponente ruft wiederum die erforderlichen Daten ab Artikelliste. Wenn Sie auf die Veröffentlichungsseite gehen, erhalten wir das bereits vorhandene Datum von Artikelliste, stellen wir eine Anfrage, um die fehlenden Daten zu erhalten und fügen sie einfach dem vorhandenen Objekt hinzu.

Warum ist dieser Ansatz besser? Wie ich oben geschrieben habe, ist dieser Ansatz schonender im Hinblick auf die heruntergeladenen Daten und ermöglicht deren Wiederverwendung. Aber darüber hinaus eröffnet es den Weg zu einigen neuen Möglichkeiten, die perfekt in eine solche Architektur passen. Zum Beispiel das Abfragen und Laden von Artikeln in den Feed, sobald sie erscheinen. Wir können die neuesten Beiträge einfach in einem „Speicher“ ablegen. Artikelliste, speichern Sie eine separate Liste neuer IDs in Artikel-IDs und den Benutzer darüber informieren. Wenn wir auf die Schaltfläche „Neue Veröffentlichungen anzeigen“ klicken, fügen wir einfach neue Ids am Anfang des Arrays der aktuellen Artikelliste ein und alles wird fast wie von Zauberhand funktionieren.

Macht das Herunterladen angenehmer

Das Tüpfelchen auf dem Refactoring-Kuchen ist das Konzept der Skelette, das den Prozess des Herunterladens von Inhalten in einem langsamen Internet etwas weniger ekelhaft macht. Es gab keine Diskussionen zu diesem Thema, der Weg von der Idee bis zum Prototyp dauerte buchstäblich zwei Stunden. Das Design zeichnete sich praktisch von selbst, und wir brachten unseren Komponenten bei, einfache, kaum flackernde Div-Blöcke zu rendern, während sie auf Daten warteten. Subjektiv reduziert dieser Belastungsansatz tatsächlich die Menge an Stresshormonen im Körper des Benutzers. Das Skelett sieht so aus:

Habr-Frontend-Entwicklerprotokolle: Refactoring und Reflektion
Habraloading

Nachdenken

Ich arbeite seit sechs Monaten in Habré und meine Freunde fragen immer noch: Na, wie gefällt es dir dort? Okay, bequem – ja. Aber es gibt etwas, das diese Arbeit von anderen unterscheidet. Ich habe in Teams gearbeitet, denen ihr Produkt völlig gleichgültig gegenüberstand und die ihre Benutzer nicht kannten oder verstanden. Aber hier ist alles anders. Hier fühlt man sich verantwortlich für das, was man tut. Im Prozess der Entwicklung einer Funktion werden Sie teilweise zu deren Eigentümer, nehmen an allen Produktbesprechungen rund um Ihre Funktionalität teil, machen Vorschläge und treffen selbst Entscheidungen. Ein Produkt, das man jeden Tag nutzt, selbst zu entwickeln, ist sehr cool, aber Code für Leute zu schreiben, die wahrscheinlich besser darin sind als man, ist einfach ein unglaubliches Gefühl (kein Sarkasmus).

Nach der Veröffentlichung all dieser Änderungen haben wir positives Feedback erhalten und es war sehr, sehr schön. Es ist inspirierend. Danke! Schreib mehr.

Ich möchte Sie daran erinnern, dass wir nach globalen Variablen beschlossen haben, die Architektur zu ändern und die Proxy-Ebene einer separaten Instanz zuzuweisen. Die „Zwei-Knoten“-Architektur wurde bereits in Form öffentlicher Betatests veröffentlicht. Jetzt kann jeder darauf umsteigen und uns helfen, das mobile Habr zu verbessern. Das ist alles für heute. Gerne beantworte ich alle deine Fragen in den Kommentaren.

Source: habr.com

Kommentar hinzufügen