Habr front-end utviklerlogger: refaktorering og reflektering

Habr front-end utviklerlogger: refaktorering og reflektering

Jeg har alltid vært interessert i hvordan Habr er strukturert fra innsiden, hvordan arbeidsflyten er strukturert, hvordan kommunikasjon er strukturert, hvilke standarder som brukes og hvordan kode generelt skrives her. Heldigvis fikk jeg en slik mulighet, fordi jeg nylig ble en del av habra-teamet. Ved å bruke eksempelet på en liten refaktorisering av mobilversjonen, skal jeg prøve å svare på spørsmålet: hvordan er det å jobbe her foran. I programmet: Node, Vue, Vuex og SSR med saus fra notater om personlig erfaring i Habr.

Det første du trenger å vite om utviklingsteamet er at vi er få. Ikke nok - dette er tre fronter, to backer og den tekniske ledelsen til alle Habr - Baxley. Det er selvfølgelig også en tester, en designer, tre Vadim, en mirakelkost, en markedsføringsspesialist og andre Bumburums. Men det er bare seks direkte bidragsytere til Habrs kilder. Dette er ganske sjeldent – ​​et prosjekt med et multimillion-dollar publikum, som fra utsiden ser ut som et gigantisk foretak, ser i realiteten mer ut som en koselig startup med en flatest mulig organisasjonsstruktur.

Som mange andre IT-selskaper, bekjenner Habr seg til smidige ideer, CI-praksis, og det er alt. Men etter mine følelser utvikler Habr som produkt seg mer i bølger enn kontinuerlig. Så, i flere spurter på rad, koder vi flittig noe, designer og redesigner, bryter noe og fikser det, løser billetter og lager nye, tråkker på en rake og skyter oss selv i føttene, for endelig å slippe funksjonen inn i produksjon. Og så kommer det en viss pause, en periode med ombygging, tid til å gjøre det som er i "viktig-ikke presserende" kvadranten.

Det er nettopp denne "off-season"-sprinten som vil bli diskutert nedenfor. Denne gangen inkluderte det en refaktorisering av mobilversjonen av Habr. Generelt har selskapet store forhåpninger til det, og i fremtiden bør det erstatte hele dyrehagen til Habrs inkarnasjoner og bli en universell løsning på tvers av plattformer. En dag vil det være adaptiv layout, PWA, offline-modus, brukertilpasning og mange andre interessante ting.

La oss sette oppgaven

En gang, på en vanlig stand-up, snakket en av frontene om problemer i arkitekturen til kommentarkomponenten til mobilversjonen. Med dette i tankene arrangerte vi et mikromøte i form av gruppepsykoterapi. Alle vekslet på å si hvor det gjorde vondt, de noterte alt på papir, de sympatiserte, de forsto, bortsett fra at ingen klappet. Resultatet ble en liste med 20 problemer, som gjorde det klart at mobile Habr fortsatt hadde en lang og vanskelig vei til suksess.

Jeg var først og fremst opptatt av effektiviteten av ressursbruk og det som kalles et jevnt grensesnitt. Hver dag, på hjem-arbeid-hjem-ruten, så jeg den gamle telefonen min som desperat forsøkte å vise 20 overskrifter i feeden. Det så omtrent slik ut:

Habr front-end utviklerlogger: refaktorering og reflekteringMobilt Habr-grensesnitt før refaktorisering

Hva foregår her? Kort fortalt serverte serveren HTML-siden til alle på samme måte, uavhengig av om brukeren var pålogget eller ikke. Deretter lastes klient JS og ber om nødvendige data på nytt, men justert for autorisasjon. Det vil si at vi faktisk gjorde den samme jobben to ganger. Grensesnittet flimret, og brukeren lastet ned godt hundre ekstra kilobyte. I detalj så alt enda mer skummelt ut.

Habr front-end utviklerlogger: refaktorering og reflekteringGammel SSR-CSR-ordning. Autorisasjon er bare mulig på trinn C3 og C4, når Node JS ikke er opptatt med å generere HTML og kan proxy-forespørsler til API.

Arkitekturen vår på den tiden ble veldig nøyaktig beskrevet av en av Habr-brukerne:

Mobilversjonen er dritt. Jeg forteller det som det er. En forferdelig kombinasjon av SSR og CSR.

Vi måtte innrømme det, uansett hvor trist det var.

Jeg vurderte alternativene, laget en billett i Jira med en beskrivelse på nivået "det er dårlig nå, gjør det riktig" og dekomponerte oppgaven i store trekk:

  • gjenbruke data,
  • minimere antallet omtegninger,
  • eliminere dupliserte forespørsler,
  • gjøre lasteprosessen mer tydelig.

La oss gjenbruke dataene

I teorien er gjengivelse på serversiden designet for å løse to problemer: å ikke lide av søkemotorbegrensninger mht. SPA-indeksering og forbedre metrikken FMP (uunngåelig forverres TTI). I et klassisk scenario som endelig formulert hos Airbnb i 2013 år (fortsatt på Backbone.js), er SSR den samme isomorfe JS-applikasjonen som kjører i Node-miljøet. Serveren sender ganske enkelt det genererte oppsettet som et svar på forespørselen. Deretter oppstår rehydrering på klientsiden, og så fungerer alt uten sideinnlasting. For Habr, som for mange andre ressurser med tekstinnhold, er servergjengivelse et kritisk element for å bygge vennlige forhold til søkemotorer.

Til tross for at det har gått mer enn seks år siden fremkomsten av teknologien, og i løpet av denne tiden har mye vann virkelig fløyet under broen i front-end-verdenen, for mange utviklere er denne ideen fortsatt innhyllet i hemmelighold. Vi sto ikke til side og rullet ut en Vue-applikasjon med SSR-støtte til produksjon, og manglet en liten detalj: Vi sendte ikke den opprinnelige tilstanden til klienten.

Hvorfor? Det finnes ikke noe eksakt svar på dette spørsmålet. Enten ønsket de ikke å øke størrelsen på svaret fra serveren, eller på grunn av en haug med andre arkitektoniske problemer, eller så tok det rett og slett ikke av. På en eller annen måte virker det ganske passende og nyttig å kaste ut tilstand og gjenbruke alt som serveren gjorde. Oppgaven er faktisk triviell - tilstand er ganske enkelt injisert inn i utførelseskonteksten, og Vue legger den automatisk til den genererte layouten som en global variabel: window.__INITIAL_STATE__.

Et av problemene som har oppstått er manglende evne til å konvertere sykliske strukturer til JSON (sirkulær referanse); ble løst ved ganske enkelt å erstatte slike strukturer med sine flate motstykker.

I tillegg, når du arbeider med UGC-innhold, bør du huske at dataene skal konverteres til HTML-enheter for ikke å bryte HTML-en. For disse formålene bruker vi he.

Minimere omtegninger

Som du kan se fra diagrammet ovenfor, i vårt tilfelle, utfører en Node JS-instans to funksjoner: SSR og "proxy" i APIen, der brukerautorisasjon skjer. Denne omstendigheten gjør det umulig å autorisere mens JS-koden kjører på serveren, siden noden er entrådet, og SSR-funksjonen er synkron. Det vil si at serveren rett og slett ikke kan sende forespørsler til seg selv mens callstack er opptatt med noe. Det viste seg at vi oppdaterte tilstanden, men grensesnittet sluttet ikke å rykke, siden dataene på klienten måtte oppdateres med hensyn til brukerøkten. Vi trengte å lære applikasjonen vår å sette de riktige dataene i den opprinnelige tilstanden, tatt i betraktning brukerens pålogging.

Det var bare to løsninger på problemet:

  • legge ved autorisasjonsdata til forespørsler på tvers av servere;
  • del Node JS-lag i to separate forekomster.

Den første løsningen krevde bruk av globale variabler på serveren, og den andre forlenget fristen for å fullføre oppgaven med minst en måned.

Hvordan ta et valg? Habr beveger seg ofte langs minst motstands vei. Uformelt er det et generelt ønske om å redusere syklusen fra idé til prototype til et minimum. Modellen for holdning til produktet minner litt om postulatene til booking.com, med den eneste forskjellen at Habr tar tilbakemeldinger fra brukere mye mer seriøst og stoler på at du som utvikler tar slike beslutninger.

Etter denne logikken og mitt eget ønske om å raskt løse problemet, valgte jeg globale variabler. Og, som ofte skjer, må du betale for dem før eller siden. Vi betalte nesten umiddelbart: vi jobbet i helgen, ryddet opp i konsekvensene, skrev postmortem og begynte å dele serveren i to deler. Feilen var veldig dum, og feilen som involverte den var ikke lett å gjenskape. Og ja, det er synd for dette, men på en eller annen måte, snublende og stønnende, gikk PoC-en min med globale variabler i produksjon og fungerer ganske vellykket mens jeg venter på overgangen til en ny "to-node"-arkitektur. Dette var et viktig skritt, for formelt sett var målet nådd – SSR lærte å levere en helt klar-til-bruk-side, og brukergrensesnittet ble mye roligere.

Habr front-end utviklerlogger: refaktorering og reflekteringMobilt Habr-grensesnitt etter den første fasen av refaktorisering

Til syvende og sist fører SSR-CSR-arkitekturen til mobilversjonen til dette bildet:

Habr front-end utviklerlogger: refaktorering og reflektering"To-node" SSR-CSR-krets. Node JS API er alltid klar for asynkron I/O og er ikke blokkert av SSR-funksjonen, siden sistnevnte er plassert i en egen instans. Spørrekjede #3 er ikke nødvendig.

Eliminer dupliserte forespørsler

Etter at manipulasjonene ble utført, provoserte ikke lenger den første gjengivelsen av siden epilepsi. Men den videre bruken av Habr i SPA-modus forårsaket fortsatt forvirring.

Siden grunnlaget for brukerflyt er overganger av skjemaet liste over artikler → artikkel → kommentarer og omvendt var det viktig å optimalisere ressursforbruket i denne kjeden i utgangspunktet.

Habr front-end utviklerlogger: refaktorering og reflekteringÅ gå tilbake til innleggsfeeden provoserer frem en ny dataforespørsel

Det var ingen grunn til å grave dypt. I skjermbildet ovenfor kan du se at applikasjonen ber om listen over artikler på nytt når du sveiper tilbake, og under forespørselen ser vi ikke artiklene, noe som betyr at tidligere data forsvinner et sted. Det ser ut til at artikkellistekomponenten bruker en lokal stat og mister den ved ødeleggelse. Faktisk brukte applikasjonen en global tilstand, men Vuex-arkitekturen ble bygget front-on: moduler er knyttet til sider, som igjen er knyttet til ruter. Dessuten er alle moduler "engangs" - hvert påfølgende besøk på siden skrev om hele modulen:

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

Totalt hadde vi en modul Artikkelliste, som inneholder objekter av typen Artikkel og modul Sideartikkel, som var en utvidet versjon av objektet Artikkel, på en måte ArtikkelFull. I det store og hele bærer ikke denne implementeringen noe forferdelig i seg selv - den er veldig enkel, man kan til og med si naiv, men ekstremt forståelig. Hvis du tilbakestiller modulen hver gang du endrer ruten, kan du til og med leve med den. Men å flytte mellom artikkelfeeder, for eksempel /feed → /alle, kaster garantert alt relatert til den personlige feeden, siden vi kun har en Artikkelliste, der du må legge inn nye data. Dette fører igjen til duplisering av forespørsler.

Etter å ha samlet alt jeg var i stand til å grave frem om emnet, formulerte jeg en ny statsstruktur og presenterte den for mine kolleger. Diskusjonene var lange, men til slutt veide argumentene for tvilen opp, og jeg begynte implementeringen.

Logikken til en løsning avsløres best i to trinn. Først prøver vi å koble Vuex-modulen fra sider og binde direkte til ruter. Ja, det blir litt mer data i butikken, gettere blir litt mer komplekse, men vi laster ikke inn artikler to ganger. For mobilversjonen er dette kanskje det sterkeste argumentet. Det vil se omtrent slik ut:

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

Men hva om artikkellister kan overlappe mellom flere ruter og hva om vi ønsker å gjenbruke objektdata Artikkel for å gjengi innleggssiden, gjøre den om til ArtikkelFull? I dette tilfellet ville det være mer logisk å bruke en slik struktur:

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

Artikkelliste her er det bare et slags oppbevaringssted for artikler. Alle artikler som ble lastet ned under brukerøkten. Vi behandler dem med største forsiktighet, fordi dette er trafikk som kan ha blitt lastet ned gjennom smerte et sted i t-banen mellom stasjonene, og vi ønsker definitivt ikke å forårsake denne smerten for brukeren igjen ved å tvinge ham til å laste inn data som han allerede har lastet ned. En gjenstand Artikkel-IDer er ganske enkelt en rekke IDer (som om "lenker") til objekter Artikkel. Denne strukturen lar deg unngå duplisering av data som er felles for ruter og gjenbruk av objektet Artikkel når du gjengir en postside ved å slå sammen utvidede data til den.

Utdataene fra listen over artikler har også blitt mer transparente: iterator-komponenten itererer gjennom arrayen med artikkel-ID-er og tegner artikkelteaser-komponenten, sender ID-en som en rekvisitt, og den underordnede komponenten henter på sin side de nødvendige dataene fra Artikkelliste. Når du går til publiseringssiden får vi den allerede eksisterende datoen fra Artikkelliste, sender vi en forespørsel om å få tak i de manglende dataene og legger dem ganske enkelt til det eksisterende objektet.

Hvorfor er denne tilnærmingen bedre? Som jeg skrev ovenfor, er denne tilnærmingen mer skånsom med hensyn til de nedlastede dataene og lar deg gjenbruke den. Men i tillegg til dette åpner det for noen nye muligheter som passer perfekt inn i en slik arkitektur. For eksempel polling og lasting av artikler i feeden slik de vises. Vi kan ganske enkelt legge de siste innleggene i en "lagring" Artikkelliste, lagre en egen liste over nye IDer i Artikkel-IDer og varsle brukeren om det. Når vi klikker på "Vis nye publikasjoner"-knappen, vil vi ganske enkelt sette inn nye Id-er i begynnelsen av rekken av den gjeldende listen over artikler, og alt vil fungere nesten magisk.

Gjør nedlasting morsommere

Prikken over i-en er konseptet med skjeletter, som gjør prosessen med å laste ned innhold på et tregt internett litt mindre ekkelt. Det var ingen diskusjoner om denne saken; veien fra idé til prototype tok bokstavelig talt to timer. Designet tegnet praktisk talt seg selv, og vi lærte komponentene våre å gjengi enkle, knapt flimrende div-blokker mens de ventet på data. Subjektivt sett reduserer denne tilnærmingen til lasting faktisk mengden stresshormoner i brukerens kropp. Skjelettet ser slik ut:

Habr front-end utviklerlogger: refaktorering og reflektering
Habraloading

Reflekterer

Jeg har jobbet i Habré i seks måneder, og vennene mine spør fortsatt: vel, hvordan liker du det der? Ok, behagelig - ja. Men det er noe som gjør dette arbeidet annerledes enn andres. Jeg jobbet i team som var fullstendig likegyldige til produktet deres, ikke visste eller forsto hvem brukerne deres var. Men her er alt annerledes. Her føler du ansvar for det du gjør. I prosessen med å utvikle en funksjon blir du delvis eieren av den, deltar i alle produktmøter knyttet til funksjonaliteten din, kommer med forslag og tar avgjørelser selv. Å lage et produkt som du bruker hver dag selv er veldig kult, men å skrive kode for folk som sannsynligvis er flinkere til det enn deg er bare en utrolig følelse (ingen sarkasme).

Etter utgivelsen av alle disse endringene fikk vi positive tilbakemeldinger, og det var veldig, veldig hyggelig. Det er inspirerende. Takk skal du ha! Skriv mer.

La meg minne deg på at etter globale variabler bestemte vi oss for å endre arkitekturen og tildele proxy-laget til en egen instans. "To-node"-arkitekturen har allerede nådd utgivelse i form av offentlig beta-testing. Nå kan hvem som helst bytte til det og hjelpe oss med å gjøre mobile Habr bedre. Det var alt for i dag. Jeg vil gjerne svare på alle spørsmålene dine i kommentarene.

Kilde: www.habr.com

Legg til en kommentar