Habr front-end ontwikkelaarslogboeken: refactoring en reflectie

Habr front-end ontwikkelaarslogboeken: refactoring en reflectie

Ik ben altijd geïnteresseerd geweest in hoe Habr van binnenuit is gestructureerd, hoe de workflow is gestructureerd, hoe de communicatie is gestructureerd, welke standaarden worden gebruikt en hoe code hier over het algemeen wordt geschreven. Gelukkig kreeg ik zo’n kans, want sinds kort ben ik onderdeel geworden van het habra-team. Aan de hand van het voorbeeld van een kleine refactoring van de mobiele versie zal ik proberen de vraag te beantwoorden: hoe is het om hier aan het front te werken. In het programma: Node, Vue, Vuex en SSR met sausje van aantekeningen over persoonlijke ervaringen in Habr.

Het eerste dat u moet weten over het ontwikkelingsteam is dat er maar weinig mensen zijn. Niet genoeg - dit zijn drie fronten, twee backs en de technische voorsprong van alle Habr - Baxley. Er is uiteraard ook een tester, een ontwerper, drie Vadim, een wonderbezem, een marketingspecialist en andere Bumburums. Maar er zijn slechts zes directe bijdragen aan de bronnen van Habr. Dit is vrij zeldzaam: een project met een miljoenenpubliek, dat er van buitenaf uitziet als een gigantische onderneming, maar in werkelijkheid meer lijkt op een gezellige startup met de platst mogelijke organisatiestructuur.

Net als veel andere IT-bedrijven belijdt Habr Agile-ideeën, CI-praktijken, en dat is alles. Maar naar mijn gevoel ontwikkelt Habr als product zich meer in golven dan continu. Dus meerdere sprints achter elkaar coderen we ijverig iets, ontwerpen en herontwerpen, maken we iets kapot en repareren we, lossen we tickets op en creëren we nieuwe, stappen we op een hark en schieten we onszelf in de voeten, om de functie eindelijk vrij te geven in productie. En dan komt er een zekere stilte, een periode van herontwikkeling, tijd om te doen wat zich in het kwadrant ‘belangrijk en niet urgent’ bevindt.

Het is precies deze sprint ‘buiten het seizoen’ die hieronder zal worden besproken. Deze keer omvatte het een refactoring van de mobiele versie van Habr. Over het algemeen heeft het bedrijf er hoge verwachtingen van, en in de toekomst zou het de hele dierentuin van Habr's incarnaties moeten vervangen en een universele platformonafhankelijke oplossing moeten worden. Op een dag zal er een adaptieve lay-out, PWA, offlinemodus, gebruikersaanpassing en vele andere interessante dingen zijn.

Laten we de taak instellen

Eens, tijdens een gewone stand-up, sprak een van de fronten over problemen in de architectuur van de commentaarcomponent van de mobiele versie. Met dit in gedachten organiseerden we een microbijeenkomst in de vorm van groepspsychotherapie. Iedereen zei om de beurt waar het pijn deed, ze schreven alles op papier, ze sympathiseerden, ze begrepen het, behalve dat niemand klapte. Het resultaat was een lijst met twintig problemen, waaruit duidelijk werd dat mobiele Habr nog een lange en lastige weg naar succes had.

Ik maakte me vooral zorgen over de efficiëntie van het gebruik van hulpbronnen en wat een soepele interface wordt genoemd. Elke dag zag ik op de route thuis-werk-thuis hoe mijn oude telefoon wanhopig probeerde twintig koppen in de feed weer te geven. Het zag er ongeveer zo uit:

Habr front-end ontwikkelaarslogboeken: refactoring en reflectieMobiele Habr-interface vóór refactoring

Wat is hier aan de hand? Kortom, de server diende de HTML-pagina voor iedereen op dezelfde manier, ongeacht of de gebruiker was ingelogd of niet. Vervolgens wordt de client JS geladen en vraagt ​​deze opnieuw de benodigde gegevens op, maar dan aangepast voor autorisatie. Dat wil zeggen dat we eigenlijk twee keer hetzelfde werk hebben gedaan. De interface flikkerde en de gebruiker downloadde ruim honderd kilobytes extra. In detail zag alles er nog griezeliger uit.

Habr front-end ontwikkelaarslogboeken: refactoring en reflectieOude SSR-MVO-regeling. Autorisatie is alleen mogelijk in de fasen C3 en C4, wanneer Node JS niet bezig is met het genereren van HTML en verzoeken aan de API kan proxyen.

Onze architectuur uit die tijd werd zeer nauwkeurig beschreven door een van de Habr-gebruikers:

De mobiele versie is onzin. Ik vertel het zoals het is. Een verschrikkelijke combinatie van SSR en CSR.

We moesten het toegeven, hoe verdrietig het ook was.

Ik beoordeelde de opties, maakte een ticket aan in Jira met een beschrijving op het niveau van “het is nu slecht, doe het goed” en ontleedde de taak in grote lijnen:

  • gegevens hergebruiken,
  • minimaliseer het aantal hertrekkingen,
  • dubbele verzoeken elimineren,
  • maak het laadproces duidelijker.

Laten we de gegevens hergebruiken

In theorie is server-side rendering ontworpen om twee problemen op te lossen: niet te lijden onder zoekmachinebeperkingen in termen van SPA-indexering en de metriek verbeteren FMP (onvermijdelijk verslechterend TTI). In een klassiek scenario is dat eindelijk het geval geformuleerd bij Airbnb in 2013 jaar (nog steeds op Backbone.js), is SSR dezelfde isomorfe JS-applicatie die draait in de Node-omgeving. De server verzendt eenvoudigweg de gegenereerde lay-out als antwoord op het verzoek. Vervolgens vindt rehydratie plaats aan de clientzijde, en dan werkt alles zonder herladen van de pagina. Voor Habr is, net als voor veel andere bronnen met tekstinhoud, serverweergave een cruciaal element bij het opbouwen van vriendschappelijke relaties met zoekmachines.

Ondanks het feit dat er meer dan zes jaar zijn verstreken sinds de komst van de technologie, en gedurende deze tijd veel water echt onder de brug is gevlogen in de front-endwereld, is dit idee voor veel ontwikkelaars nog steeds gehuld in geheimhouding. We hebben niet terzijde gestaan ​​en een Vue-applicatie met SSR-ondersteuning uitgerold naar productie, waarbij we één klein detail misten: we hebben de initiële status niet naar de klant gestuurd.

Waarom? Er is geen exact antwoord op deze vraag. Ofwel wilden ze de respons van de server niet vergroten, ofwel vanwege een heleboel andere architectonische problemen, ofwel het kwam simpelweg niet van de grond. Op de een of andere manier lijkt het heel passend en nuttig om de staat weg te gooien en alles te hergebruiken wat de server deed. De taak is eigenlijk triviaal - toestand wordt eenvoudigweg geïnjecteerd in de uitvoeringscontext, en Vue voegt het automatisch toe aan de gegenereerde lay-out als een globale variabele: window.__INITIAL_STATE__.

Een van de problemen die zich heeft voorgedaan is het onvermogen om cyclische structuren om te zetten in JSON (cirkelverwijzing); werd opgelost door dergelijke constructies eenvoudigweg te vervangen door hun platte tegenhangers.

Bovendien moet u er bij het omgaan met UGC-inhoud aan denken dat de gegevens moeten worden geconverteerd naar HTML-entiteiten om de HTML niet te verbreken. Voor deze doeleinden gebruiken wij he.

Herhalingen minimaliseren

Zoals u in het bovenstaande diagram kunt zien, voert één Node JS-instantie in ons geval twee functies uit: SSR en “proxy” in de API, waar gebruikersautorisatie plaatsvindt. Deze omstandigheid maakt het onmogelijk om autorisatie te verlenen terwijl de JS-code op de server wordt uitgevoerd, aangezien het knooppunt single-threaded is en de SSR-functie synchroon is. Dat wil zeggen dat de server eenvoudigweg geen verzoeken naar zichzelf kan sturen terwijl de callstack ergens mee bezig is. Het bleek dat we de status hadden bijgewerkt, maar de interface stopte niet met trillen, omdat de gegevens op de client moesten worden bijgewerkt, rekening houdend met de gebruikerssessie. We moesten onze applicatie leren de juiste gegevens in de beginstatus te plaatsen, rekening houdend met de login van de gebruiker.

Er waren slechts twee oplossingen voor het probleem:

  • autorisatiegegevens aan cross-server-verzoeken koppelen;
  • splits Node JS-lagen in twee afzonderlijke instanties.

De eerste oplossing vereiste het gebruik van globale variabelen op de server, en de tweede verlengde de deadline voor het voltooien van de taak met minstens een maand.

Hoe maak je een keuze? Habr volgt vaak de weg van de minste weerstand. Informeel bestaat er een algemene wens om de cyclus van idee naar prototype tot een minimum te beperken. Het houdingsmodel ten opzichte van het product doet enigszins denken aan de postulaten van booking.com, met als enige verschil dat Habr gebruikersfeedback veel serieuzer neemt en erop vertrouwt dat u als ontwikkelaar dergelijke beslissingen neemt.

Op basis van deze logica en mijn eigen verlangen om het probleem snel op te lossen, heb ik voor globale variabelen gekozen. En zoals vaak gebeurt, moet u er vroeg of laat voor betalen. We betaalden vrijwel onmiddellijk: we werkten in het weekend, ruimden de gevolgen op, schreven postmortem en begon de server in twee delen te verdelen. De fout was erg dom en de bug die ermee gepaard ging, was niet gemakkelijk te reproduceren. En ja, het is jammer, maar op de een of andere manier, struikelend en kreunend, ging mijn PoC met globale variabelen toch in productie en werkt behoorlijk succesvol in afwachting van de overstap naar een nieuwe "twee-knooppunt" -architectuur. Dit was een belangrijke stap, omdat formeel het doel was bereikt: SSR leerde een volledig kant-en-klare pagina op te leveren en de gebruikersinterface werd veel rustiger.

Habr front-end ontwikkelaarslogboeken: refactoring en reflectieMobiele Habr-interface na de eerste fase van refactoring

Uiteindelijk leidt de SSR-CSR-architectuur van de mobiele versie tot dit beeld:

Habr front-end ontwikkelaarslogboeken: refactoring en reflectieSSR-CSR-circuit met twee knooppunten. De Node JS API is altijd gereed voor asynchrone I/O en wordt niet geblokkeerd door de SSR-functie, aangezien deze zich in een aparte instance bevindt. Queryketen #3 is niet nodig.

Het elimineren van dubbele verzoeken

Nadat de manipulaties waren uitgevoerd, veroorzaakte de initiële weergave van de pagina niet langer epilepsie. Maar het verdere gebruik van Habr in de SPA-modus zorgde nog steeds voor verwarring.

Omdat de basis van de gebruikersstroom de overgangen van het formulier zijn lijst met artikelen → artikel → opmerkingen en omgekeerd was het in de eerste plaats belangrijk om het grondstoffenverbruik van deze keten te optimaliseren.

Habr front-end ontwikkelaarslogboeken: refactoring en reflectieTerugkeren naar de postfeed lokt een nieuw gegevensverzoek uit

Het was niet nodig om diep te graven. In de screencast hierboven kun je zien dat de applicatie bij het terug swipen opnieuw de lijst met artikelen opvraagt, en tijdens het opvragen zien wij de artikelen niet waardoor de eerdere gegevens ergens verdwijnen. Het lijkt erop dat de artikellijstcomponent een lokale status gebruikt en deze bij vernietiging verliest. In feite gebruikte de applicatie een globale status, maar de Vuex-architectuur was frontaal gebouwd: modules zijn gekoppeld aan pagina's, die op hun beurt zijn gekoppeld aan routes. Bovendien zijn alle modules "wegwerpbaar" - bij elk volgend bezoek aan de pagina wordt de hele module herschreven:

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

In totaal hadden we een module ArtikelenLijst, dat objecten van het type bevat Artikel en moduul PaginaArtikel, wat een uitgebreide versie van het object was Artikel, soort van ArtikelVol. Over het algemeen heeft deze implementatie op zichzelf niets vreselijks in zich - het is heel eenvoudig, je zou zelfs naïef kunnen zeggen, maar uiterst begrijpelijk. Als je de module elke keer dat je de route wijzigt opnieuw instelt, kun je er zelfs mee leven. Maar bijvoorbeeld het verplaatsen tussen artikelfeeds /feed → /alles, gooit gegarandeerd alles weg wat met de persoonlijke feed te maken heeft, aangezien we er maar één hebben ArtikelenLijst, waarin u nieuwe gegevens moet plaatsen. Dit leidt opnieuw tot dubbele verzoeken.

Nadat ik alles had verzameld wat ik over dit onderwerp kon opgraven, formuleerde ik een nieuwe staatsstructuur en presenteerde deze aan mijn collega's. De discussies duurden lang, maar uiteindelijk wogen de argumenten vóór de twijfels zwaarder en begon ik met de implementatie.

De logica van een oplossing kan het beste in twee stappen worden onthuld. Eerst proberen we de Vuex-module los te koppelen van pagina's en direct aan routes te binden. Ja, er komt iets meer data in de winkel, getters worden iets complexer, maar we zullen artikelen niet twee keer laden. Voor de mobiele versie is dit misschien wel het sterkste argument. Het zal er ongeveer zo uitzien:

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

Maar wat als artikellijsten kunnen overlappen tussen meerdere routes en wat als we objectgegevens willen hergebruiken? Artikel om de berichtpagina weer te geven en er een ArtikelVol? In dit geval zou het logischer zijn om een ​​dergelijke structuur te gebruiken:

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

ArtikelenLijst hier is het gewoon een soort opslagplaats van artikelen. Alle artikelen die tijdens de gebruikerssessie zijn gedownload. We behandelen ze met de grootste zorg, omdat dit verkeer is dat mogelijk door pijn ergens in de metro tussen stations is gedownload, en we willen de gebruiker zeker niet nogmaals deze pijn bezorgen door hem te dwingen gegevens te laden die hij al heeft. gedownload. Een voorwerp ArtikelIds is eenvoudigweg een reeks ID's (alsof het "links" zijn) naar objecten Artikel. Met deze structuur kunt u voorkomen dat gegevens die gebruikelijk zijn voor routes worden gedupliceerd en dat het object opnieuw wordt gebruikt Artikel bij het weergeven van een berichtpagina door er uitgebreide gegevens in samen te voegen.

De uitvoer van de lijst met artikelen is ook transparanter geworden: de iteratorcomponent doorloopt de array met artikel-ID's en tekent de artikelteasercomponent, waarbij de ID als prop wordt doorgegeven, en de onderliggende component haalt op zijn beurt de benodigde gegevens op uit ArtikelenLijst. Wanneer u naar de publicatiepagina gaat, halen wij de reeds bestaande datum eruit ArtikelenLijst, doen we een verzoek om de ontbrekende gegevens te verkrijgen en deze eenvoudig toe te voegen aan het bestaande object.

Waarom is deze aanpak beter? Zoals ik hierboven schreef, is deze aanpak zachter met betrekking tot de gedownloade gegevens en kunt u deze hergebruiken. Maar daarnaast opent het de weg naar enkele nieuwe mogelijkheden die perfect in een dergelijke architectuur passen. Bijvoorbeeld het pollen en laden van artikelen in de feed zodra ze verschijnen. We kunnen eenvoudig de laatste berichten in een “opslag” plaatsen ArtikelenLijst, sla een aparte lijst met nieuwe ID's op in ArtikelIds en breng de gebruiker hiervan op de hoogte. Wanneer we op de knop ‘Nieuwe publicaties weergeven’ klikken, voegen we eenvoudigweg nieuwe ID’s in aan het begin van de array van de huidige lijst met artikelen en alles werkt bijna magisch.

Maakt downloaden leuker

De kers op de refactoring-taart is het concept van skeletten, waardoor het proces van het downloaden van inhoud op een langzaam internet iets minder walgelijk wordt. Er waren geen discussies hierover; het traject van idee naar prototype duurde letterlijk twee uur. Het ontwerp tekende zich praktisch vanzelf en we leerden onze componenten om eenvoudige, nauwelijks flikkerende div-blokken weer te geven terwijl ze op gegevens wachtten. Subjectief gezien vermindert deze benadering van laden feitelijk de hoeveelheid stresshormonen in het lichaam van de gebruiker. Het skelet ziet er als volgt uit:

Habr front-end ontwikkelaarslogboeken: refactoring en reflectie
Habraladen

Reflecterend

Ik werk nu een half jaar in Habré en mijn vrienden vragen nog steeds: nou, hoe vind je het daar? Oké, comfortabel - ja. Maar er is iets dat dit werk anders maakt dan andere. Ik werkte in teams die volkomen onverschillig stonden tegenover hun product, niet wisten of begrepen wie hun gebruikers waren. Maar hier is alles anders. Hier voel je je verantwoordelijk voor wat je doet. Tijdens het ontwikkelen van een feature wordt u gedeeltelijk de eigenaar ervan, neemt u deel aan alle productvergaderingen die verband houden met uw functionaliteit, doet u suggesties en neemt u zelf beslissingen. Zelf een product maken dat je elke dag gebruikt is heel gaaf, maar code schrijven voor mensen die er waarschijnlijk beter in zijn dan jij is gewoon een ongelooflijk gevoel (geen sarcasme).

Na de release van al deze wijzigingen kregen we positieve feedback, en het was heel erg leuk. Het is inspirerend. Bedankt! Schrijf meer.

Laat me u eraan herinneren dat we na globale variabelen besloten om de architectuur te veranderen en de proxylaag aan een aparte instantie toe te wijzen. De architectuur met twee knooppunten is al vrijgegeven in de vorm van openbare bètatests. Nu kan iedereen ernaar overstappen en ons helpen mobiel Habr beter te maken. Dat is alles voor vandaag. Ik beantwoord graag al je vragen in de reacties.

Bron: www.habr.com

Voeg een reactie