Habr regjistrat e zhvilluesve të pjesës së përparme: rifaktorimi dhe reflektimi

Habr regjistrat e zhvilluesve të pjesës së përparme: rifaktorimi dhe reflektimi

Gjithmonë kam qenë i interesuar se si strukturohet Habr nga brenda, si strukturohet rrjedha e punës, si strukturohen komunikimet, cilat standarde përdoren dhe si shkruhet kodi në përgjithësi këtu. Fatmirësisht e kam pasur një mundësi të tillë, sepse së fundmi jam bërë pjesë e ekipit të habrave. Duke përdorur shembullin e një rifaktorimi të vogël të versionit celular, do të përpiqem t'i përgjigjem pyetjes: si është të punosh këtu në pjesën e përparme. Në program: Node, Vue, Vuex dhe SSR me salcë nga shënimet për përvojën personale në Habr.

Gjëja e parë që duhet të dini për ekipin e zhvillimit është se jemi pak prej nesh. Nuk mjafton - këto janë tre fronte, dy të pasme dhe epërsia teknike e të gjithë Habr - Baxley. Ka, natyrisht, edhe një testues, një stilist, tre Vadim, një fshesë mrekullie, një specialist marketingu dhe bumburum të tjerë. Por ka vetëm gjashtë kontribues të drejtpërdrejtë në burimet e Habrit. Kjo është mjaft e rrallë - një projekt me një audiencë shumëmilionëshe, i cili nga jashtë duket si një ndërmarrje gjigante, në realitet duket më shumë si një startup komod me strukturën organizative më të sheshtë të mundshme.

Ashtu si shumë kompani të tjera të IT-së, Habr rrëfen idetë e shkathëta, praktikat CI, dhe kjo është e gjitha. Por sipas ndjenjave të mia, Habr si produkt po zhvillohet më shumë në valë sesa vazhdimisht. Pra, për disa sprinte radhazi, ne kodojmë me zell diçka, dizajnojmë dhe ridizajnojmë, thyejmë diçka dhe e rregullojmë atë, zgjidhim biletat dhe krijojmë të reja, shkelim një grabujë dhe gjuajmë veten në këmbë, në mënyrë që më në fund ta lëshojmë funksionin në prodhimit. Dhe pastaj vjen një qetësi e caktuar, një periudhë rizhvillimi, koha për të bërë atë që është në kuadratin "e rëndësishme-jo urgjente".

Është pikërisht ky sprint "jashtë sezonit" që do të diskutohet më poshtë. Këtë herë ai përfshinte një rifaktorim të versionit celular të Habr. Në përgjithësi, kompania ka shpresa të mëdha për të, dhe në të ardhmen duhet të zëvendësojë të gjithë kopshtin zoologjik të mishërimeve të Habr dhe të bëhet një zgjidhje universale ndër-platformë. Një ditë do të ketë paraqitje adaptive, PWA, modaliteti offline, personalizimi i përdoruesit dhe shumë gjëra të tjera interesante.

Le të vendosim detyrën

Një herë, në një stand-up të zakonshëm, një nga të parët foli për problemet në arkitekturën e komponentit të komenteve të versionit celular. Me këtë në mendje organizuam një mikro-takim në formatin e psikoterapisë në grup. Të gjithë thoshin me radhë ku u dhemb, shënuan gjithçka në letër, simpatizuan, kuptuan, veç se askush nuk duartrokiste. Rezultati ishte një listë me 20 probleme, të cilat e bënë të qartë se celulari Habr kishte ende një rrugë të gjatë dhe të mprehtë drejt suksesit.

Unë isha i shqetësuar kryesisht për efikasitetin e burimeve dhe atë që quhet një ndërfaqe e qetë. Çdo ditë, në rrugën shtëpi-punë-shtëpi, shihja telefonin tim të vjetër duke u përpjekur dëshpërimisht të shfaqte 20 tituj në burim. Dukej diçka si kjo:

Habr regjistrat e zhvilluesve të pjesës së përparme: rifaktorimi dhe reflektimiNdërfaqja Mobile Habr përpara rifaktorimit

Cfare po ndodh ketu? Shkurtimisht, serveri u shërbeu të gjithëve faqen HTML në të njëjtën mënyrë, pavarësisht nëse përdoruesi ishte i regjistruar apo jo. Më pas klienti JS ngarkohet dhe kërkon sërish të dhënat e nevojshme, por rregullohet për autorizim. Kjo do të thotë, ne në fakt kemi bërë të njëjtën punë dy herë. Ndërfaqja dridhej dhe përdoruesi shkarkoi njëqind kilobajt shtesë. Në detaje gjithçka dukej edhe më rrëqethëse.

Habr regjistrat e zhvilluesve të pjesës së përparme: rifaktorimi dhe reflektimiSkema e vjetër SSR-CSR. Autorizimi është i mundur vetëm në fazat C3 dhe C4, kur Node JS nuk është i zënë me gjenerimin e HTML-së dhe mund të përcaktojë kërkesat në API.

Arkitektura jonë e asaj kohe u përshkrua shumë saktë nga një nga përdoruesit e Habrit:

Versioni celular është i kotë. Unë po e them ashtu siç është. Një kombinim i tmerrshëm i SSR dhe CSR.

Duhet ta pranonim, sado e trishtueshme të ishte.

Vlerësova opsionet, krijova një biletë në Jira me një përshkrim në nivelin "është keq tani, bëje siç duhet" dhe e zbërtheva detyrën në vija të gjera:

  • ripërdorimin e të dhënave,
  • minimizoni numrin e rivizatimeve,
  • eliminimi i kërkesave të dyfishta,
  • e bëjnë më të qartë procesin e ngarkimit.

Le të ripërdorim të dhënat

Në teori, interpretimi nga ana e serverit është krijuar për të zgjidhur dy probleme: të mos vuash nga kufizimet e motorit të kërkimit në drejtim të Indeksimi SPA dhe përmirësoni metrikën FMP (duke përkeqësuar në mënyrë të pashmangshme TTI). Në një skenar klasik që më në fund formuluar në Airbnb në 2013 vit (ende në Backbone.js), SSR është i njëjti aplikacion izomorfik JS që funksionon në mjedisin Node. Serveri thjesht dërgon paraqitjen e krijuar si përgjigje ndaj kërkesës. Pastaj rihidratimi ndodh në anën e klientit dhe më pas gjithçka funksionon pa ringarkime të faqeve. Për Habr, si për shumë burime të tjera me përmbajtje teksti, përkthimi i serverit është një element kritik në ndërtimin e marrëdhënieve miqësore me motorët e kërkimit.

Përkundër faktit se kanë kaluar më shumë se gjashtë vjet nga ardhja e teknologjisë, dhe gjatë kësaj kohe shumë ujë ka fluturuar vërtet nën urë në botën e përparme, për shumë zhvillues kjo ide është ende e fshehur në fshehtësi. Ne nuk qëndruam mënjanë dhe lëshuam një aplikacion Vue me mbështetje SSR në prodhim, duke humbur një detaj të vogël: nuk ia dërguam gjendjen fillestare klientit.

Pse? Nuk ka përgjigje të saktë për këtë pyetje. Ose nuk donin të rrisnin madhësinë e përgjigjes nga serveri, ose për shkak të një sërë problemesh të tjera arkitekturore, ose thjesht nuk u ngrit. Në një mënyrë apo tjetër, hedhja jashtë gjendjes dhe ripërdorimi i gjithçkaje që bëri serveri duket mjaft e përshtatshme dhe e dobishme. Detyra është në të vërtetë e parëndësishme - gjendja thjesht injektohet në kontekstin e ekzekutimit, dhe Vue automatikisht e shton atë në paraqitjen e gjeneruar si një ndryshore globale: window.__INITIAL_STATE__.

Një nga problemet që ka lindur është pamundësia për të kthyer strukturat ciklike në JSON (referencë rrethore); u zgjidh thjesht duke zëvendësuar struktura të tilla me homologët e tyre të sheshtë.

Përveç kësaj, kur merreni me përmbajtjen UGC, duhet të mbani mend se të dhënat duhet të konvertohen në entitete HTML në mënyrë që të mos prishet HTML. Për këto qëllime ne përdorim he.

Minimizimi i rivizatimeve

Siç mund ta shihni nga diagrami i mësipërm, në rastin tonë, një shembull Node JS kryen dy funksione: SSR dhe "proxy" në API, ku ndodh autorizimi i përdoruesit. Kjo rrethanë e bën të pamundur autorizimin gjatë kohës që kodi JS po ekzekutohet në server, pasi nyja është me një fije dhe funksioni SSR është sinkron. Kjo do të thotë, serveri thjesht nuk mund t'i dërgojë kërkesa vetes ndërsa callstack është i zënë me diçka. Doli që ne azhurnuam gjendjen, por ndërfaqja nuk ndaloi së tunduri, pasi të dhënat për klientin duhej të përditësoheshin duke marrë parasysh seancën e përdoruesit. Na duhej të mësonim aplikacionin tonë për të vendosur të dhënat e sakta në gjendjen fillestare, duke marrë parasysh hyrjen e përdoruesit.

Kishte vetëm dy zgjidhje për problemin:

  • bashkëngjitni të dhënat e autorizimit në kërkesat ndër-server;
  • ndani shtresat Node JS në dy instanca të veçanta.

Zgjidhja e parë kërkonte përdorimin e variablave globale në server, dhe e dyta zgjati afatin për përfundimin e detyrës me të paktën një muaj.

Si të bëni një zgjedhje? Habr shpesh lëviz përgjatë rrugës së rezistencës më të vogël. Joformalisht, ekziston një dëshirë e përgjithshme për të reduktuar në minimum ciklin nga ideja në prototip. Modeli i qëndrimit ndaj produktit të kujton disi postulatet e booking.com, me ndryshimin e vetëm që Habr i merr shumë më seriozisht reagimet e përdoruesve dhe ju beson juve, si zhvillues, për të marrë vendime të tilla.

Duke ndjekur këtë logjikë dhe dëshirën time për të zgjidhur shpejt problemin, zgjodha variablat globale. Dhe, siç ndodh shpesh, ju duhet të paguani për to herët a vonë. Paguam pothuajse menjëherë: kemi punuar në fundjavë, kemi pastruar pasojat, shkruante pas vdekjes dhe filloi të ndajë serverin në dy pjesë. Gabimi ishte shumë budalla dhe problemi që përfshinte nuk ishte i lehtë për t'u riprodhuar. Dhe po, është turp për këtë, por në një mënyrë ose në një tjetër, duke u penguar dhe duke rënkuar, PoC-ja ime me variabla globale megjithatë hyri në prodhim dhe po punon me mjaft sukses duke pritur kalimin në një arkitekturë të re "me dy nyje". Ky ishte një hap i rëndësishëm, sepse zyrtarisht qëllimi u arrit - SSR mësoi të jepte një faqe plotësisht të gatshme për përdorim, dhe UI u bë shumë më e qetë.

Habr regjistrat e zhvilluesve të pjesës së përparme: rifaktorimi dhe reflektimiNdërfaqja Mobile Habr pas fazës së parë të rifaktorimit

Në fund të fundit, arkitektura SSR-CSR e versionit celular çon në këtë pamje:

Habr regjistrat e zhvilluesve të pjesës së përparme: rifaktorimi dhe reflektimiQarku SSR-CSR “me dy nyje”. Node JS API është gjithmonë gati për hyrje/dalje asinkrone dhe nuk bllokohet nga funksioni SSR, pasi ky i fundit ndodhet në një shembull të veçantë. Zinxhiri i pyetjeve #3 nuk është i nevojshëm.

Eliminimi i kërkesave të dyfishta

Pasi u kryen manipulimet, renderimi fillestar i faqes nuk provokoi më epilepsi. Por përdorimi i mëtejshëm i Habr në modalitetin SPA ende shkaktoi konfuzion.

Meqenëse baza e rrjedhës së përdoruesit janë tranzicionet e formës lista e artikujve → artikull → komente dhe anasjelltas, ishte e rëndësishme që në radhë të parë të optimizohej konsumi i burimeve të këtij zinxhiri.

Habr regjistrat e zhvilluesve të pjesës së përparme: rifaktorimi dhe reflektimiKthimi në furnizimin e postimit provokon një kërkesë të re të dhënash

Nuk kishte nevojë të gërmohej thellë. Në ekranin e mësipërm mund të shihni që aplikacioni rikërkon listën e artikujve kur tërhiqet prapa dhe gjatë kërkesës ne nuk i shohim artikujt, që do të thotë se të dhënat e mëparshme zhduken diku. Duket sikur komponenti i listës së artikujve përdor një gjendje lokale dhe e humb atë në shkatërrim. Në fakt, aplikacioni përdorte një gjendje globale, por arkitektura Vuex u ndërtua kokë më kokë: modulet janë të lidhura me faqe, të cilat nga ana tjetër janë të lidhura me rrugët. Për më tepër, të gjitha modulet janë "të disponueshme" - çdo vizitë pasuese në faqe rishkruan të gjithë modulin:

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

Në total, ne kishim një modul Lista e Artikujve, i cili përmban objekte të tipit Artikull dhe modul FaqeArtikull, i cili ishte një version i zgjeruar i objektit Artikull, lloj Artikulli i plotë. Në përgjithësi, ky zbatim nuk mbart asgjë të tmerrshme në vetvete - është shumë e thjeshtë, madje mund të thuhet naive, por jashtëzakonisht e kuptueshme. Nëse e rivendosni modulin sa herë që ndryshoni itinerarin, atëherë mund të jetoni edhe me të. Megjithatë, për shembull, lëvizja midis burimeve të artikujve /ushqim → /të gjitha, është e garantuar për të hedhur çdo gjë që lidhet me ushqimin personal, pasi ne kemi vetëm një Lista e Artikujve, në të cilën duhet të vendosni të dhëna të reja. Kjo përsëri na çon në dyfishim të kërkesave.

Pasi mblodha gjithçka që arrita të gërmoj për këtë temë, formulova një strukturë të re shtetërore dhe ua prezantova kolegëve të mi. Diskutimet ishin të gjata, por në fund argumentet në favor i tejkaluan dyshimet dhe fillova zbatimin.

Logjika e një zgjidhjeje zbulohet më së miri në dy hapa. Së pari ne përpiqemi të shkëputim modulin Vuex nga faqet dhe të lidhim drejtpërdrejt me rrugët. Po, do të ketë pak më shumë të dhëna në dyqan, marrësit do të bëhen pak më të ndërlikuar, por ne nuk do t'i ngarkojmë artikujt dy herë. Për versionin celular, ky është ndoshta argumenti më i fortë. Do të duket diçka si kjo:

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

Por çka nëse listat e artikujve mund të mbivendosen midis rrugëve të shumta dhe çka nëse duam të ripërdorim të dhënat e objektit Artikull për të dhënë faqen e postimit, duke e kthyer atë në Artikulli i plotë? Në këtë rast, do të ishte më logjike të përdoret një strukturë e tillë:

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

Lista e Artikujve këtu është thjesht një lloj depo artikujsh. Të gjithë artikujt që u shkarkuan gjatë sesionit të përdoruesit. Ne i trajtojmë ata me kujdesin maksimal, sepse ky është trafiku që mund të jetë shkarkuar me dhimbje diku në metro midis stacioneve dhe definitivisht nuk duam t'i shkaktojmë përsëri këtë dhimbje përdoruesit duke e detyruar atë të ngarkojë të dhënat që ai ka tashmë. të shkarkuara. Nje objekt Identifikimi i artikujve është thjesht një grup ID-sh (sikur "lidhje") me objekte Artikull. Kjo strukturë ju lejon të shmangni dyfishimin e të dhënave të zakonshme për rrugët dhe ripërdorimin e objektit Artikull kur jepni një faqe postimi duke bashkuar të dhëna të zgjeruara në të.

Dalja e listës së artikujve është bërë gjithashtu më transparente: komponenti iterator përsëritet përmes grupit me ID-të e artikujve dhe vizaton komponentin ngacmues të artikujve, duke kaluar ID-në si mbështetje, dhe komponenti fëmijë, nga ana tjetër, merr të dhënat e nevojshme nga Lista e Artikujve. Kur shkoni në faqen e publikimit, ne marrim datën ekzistuese nga Lista e Artikujve, bëjmë një kërkesë për të marrë të dhënat që mungojnë dhe thjesht i shtojmë ato në objektin ekzistues.

Pse është më e mirë kjo qasje? Siç shkrova më lart, kjo qasje është më e butë në lidhje me të dhënat e shkarkuara dhe ju lejon t'i ripërdorni ato. Por përveç kësaj, ajo hap rrugën për disa mundësi të reja që përshtaten në mënyrë të përkryer në një arkitekturë të tillë. Për shembull, sondazhi dhe ngarkimi i artikujve në furnizim ashtu siç shfaqen. Ne thjesht mund t'i vendosim postimet e fundit në një "magazinë" Lista e Artikujve, ruani një listë të veçantë të ID-ve të reja në Identifikimi i artikujve dhe njoftoni përdoruesin për këtë. Kur klikojmë në butonin "Trego botimet e reja", thjesht do të fusim ID-të e reja në fillim të grupit të listës aktuale të artikujve dhe gjithçka do të funksionojë pothuajse në mënyrë magjike.

Duke e bërë shkarkimin më të këndshëm

Qershia mbi tortën e rifaktorimit është koncepti i skeleteve, që e bën procesin e shkarkimit të përmbajtjes në një internet të ngadaltë pak më pak të neveritshëm. Nuk pati diskutime për këtë çështje; rruga nga ideja në prototip zgjati fjalë për fjalë dy orë. Dizajni praktikisht vizatohej vetë, dhe ne i mësuam komponentët tanë të jepnin blloqe div të thjeshta, që mezi dridheshin ndërsa prisnin të dhënat. Subjektivisht, kjo qasje ndaj ngarkimit në fakt zvogëlon sasinë e hormoneve të stresit në trupin e përdoruesit. Skeleti duket si ky:

Habr regjistrat e zhvilluesve të pjesës së përparme: rifaktorimi dhe reflektimi
Habraloading

Duke reflektuar

Unë kam gjashtë muaj që punoj në Habré dhe miqtë e mi ende pyesin: mirë, si ju pëlqen atje? Mirë, rehat - po. Por ka diçka që e bën këtë vepër të ndryshme nga të tjerat. Kam punuar në ekipe që ishin krejtësisht indiferente ndaj produktit të tyre, nuk e dinin ose nuk e kuptonin se cilët ishin përdoruesit e tyre. Por këtu gjithçka është ndryshe. Këtu ndiheni përgjegjës për atë që bëni. Në procesin e zhvillimit të një veçorie, ju bëheni pjesërisht pronar i tij, merrni pjesë në të gjitha takimet e produktit që lidhen me funksionalitetin tuaj, bëni sugjerime dhe merrni vetë vendime. Të bësh një produkt që e përdor çdo ditë vetë është shumë e lezetshme, por shkrimi i kodit për njerëzit që ndoshta janë më të mirë se ju në të është thjesht një ndjenjë e pabesueshme (pa sarkazëm).

Pas publikimit të të gjitha këtyre ndryshimeve, ne morëm reagime pozitive, dhe ishte shumë, shumë bukur. Është frymëzuese. Faleminderit! Shkruaj më shumë.

Më lejoni t'ju kujtoj se pas variablave globale vendosëm të ndryshojmë arkitekturën dhe të ndajmë shtresën proxy në një shembull të veçantë. Arkitektura "me dy nyje" tashmë ka arritur lëshimin në formën e testimit publik beta. Tani çdokush mund të kalojë në të dhe të na ndihmojë ta përmirësojmë celularin Habr. Kaq për sot. Do të jem i lumtur t'u përgjigjem të gjitha pyetjeve tuaja në komente.

Burimi: www.habr.com

Shto një koment