Registres de desenvolupadors de front-end Habr: refactorització i reflexió

Registres de desenvolupadors de front-end Habr: refactorització i reflexió

Sempre m'ha interessat com s'estructura Habr des de dins, com s'estructura el flux de treball, com s'estructuren les comunicacions, quins estàndards s'utilitzen i com s'escriu el codi generalment aquí. Afortunadament, vaig tenir aquesta oportunitat, perquè fa poc vaig formar part de l'equip d'habra. Utilitzant l'exemple d'una petita refactorització de la versió mòbil, intentaré respondre a la pregunta: com és treballar aquí al davant. Al programa: Node, Vue, Vuex i SSR amb salsa de notes sobre l'experiència personal a Habr.

El primer que has de saber sobre l'equip de desenvolupament és que som pocs. No n'hi ha prou: són tres davanters, dos posteriors i el lideratge tècnic de tots els Habr - Baxley. També hi ha, per descomptat, un provador, un dissenyador, tres Vadim, una escombra miracle, un especialista en màrqueting i altres Bumburums. Però només hi ha sis col·laboradors directes a les fonts d'Habr. Això és força rar: un projecte amb un públic multimilionari, que des de fora sembla una empresa gegant, en realitat sembla més una startup acollidora amb l'estructura organitzativa més plana possible.

Com moltes altres empreses de TI, Habr professa idees àgils, pràctiques de CI, i això és tot. Però segons els meus sentiments, Habr com a producte s'està desenvolupant més en onades que no pas contínuament. Per tant, durant diversos sprints seguits, codifiquem una cosa amb diligència, dissenyem i redissenyem, trenquem alguna cosa i ho arreglem, resolem entrades i en creem de noves, trepitgem un rastell i ens disparem als peus, per finalment llançar la funció a producció. I després ve una certa calma, un període de reurbanització, el moment de fer allò que està en el quadrant “important-no urgent”.

És precisament aquest sprint "fora de temporada" el que es parlarà a continuació. Aquesta vegada va incloure una refactorització de la versió mòbil d'Habr. En general, l'empresa té moltes esperances en això, i en el futur hauria de substituir tot el zoo de les encarnacions d'Habr i convertir-se en una solució multiplataforma universal. Algun dia hi haurà disseny adaptatiu, PWA, mode fora de línia, personalització de l'usuari i moltes altres coses interessants.

Posem la tasca

Una vegada, en un stand-up normal, un dels davanters va parlar de problemes en l'arquitectura del component de comentaris de la versió mòbil. Amb això, vam organitzar una micro-trobada en format de psicoteràpia grupal. Tothom es tornava a dir on feia mal, ho anotaven tot en paper, simpatitzaven, entenien, excepte que ningú aplaudia. El resultat va ser una llista de 20 problemes, que va deixar clar que Habr mòbil encara tenia un llarg i espinós camí cap a l'èxit.

Em preocupava principalment l'eficiència de l'ús dels recursos i el que s'anomena una interfície suau. Cada dia, a la ruta casa-feina-casa, veia el meu telèfon antic intentant desesperadament mostrar 20 titulars al canal. Semblava una cosa així:

Registres de desenvolupadors de front-end Habr: refactorització i reflexióInterfície Habr mòbil abans de la refactorització

Que està passant aquí? En resum, el servidor va servir la pàgina HTML a tothom de la mateixa manera, independentment de si l'usuari havia iniciat sessió o no. A continuació, es carrega el client JS i torna a sol·licitar les dades necessàries, però s'ajusten per a l'autorització. És a dir, en realitat vam fer la mateixa feina dues vegades. La interfície va parpellejar i l'usuari va descarregar uns cent kilobytes addicionals. En detall, tot semblava encara més esgarrifós.

Registres de desenvolupadors de front-end Habr: refactorització i reflexióAntic esquema SSR-CSR. L'autorització només és possible a les etapes C3 i C4, quan Node JS no està ocupat generant HTML i pot enviar sol·licituds a l'API.

La nostra arquitectura d'aquella època va ser descrita amb molta precisió per un dels usuaris d'Habr:

La versió mòbil és una merda. Ho dic tal com és. Una terrible combinació de RSS i RSC.

Ho vam haver d'admetre, per molt trist que fos.

Vaig avaluar les opcions, vaig crear un bitllet a Jira amb una descripció a nivell de "ara està malament, fes-ho bé" i vaig descompondre la tasca a grans trets:

  • reutilitzar dades,
  • minimitzar el nombre de redibuixos,
  • eliminar les sol·licituds duplicades,
  • fer que el procés de càrrega sigui més evident.

Reutilitzem les dades

En teoria, la representació del costat del servidor està dissenyada per resoldre dos problemes: no patir limitacions del motor de cerca en termes de Indexació SPA i millorar la mètrica FMP (empitjorant inevitablement TTI). En un escenari clàssic que finalment formulat a Airbnb el 2013 any (encara a Backbone.js), SSR és la mateixa aplicació JS isomòrfica que s'executa a l'entorn Node. El servidor simplement envia el disseny generat com a resposta a la sol·licitud. Aleshores, la rehidratació es produeix al costat del client i tot funciona sense recàrregues de pàgines. Per a Habr, com per a molts altres recursos amb contingut de text, la representació del servidor és un element crític per construir relacions amistoses amb els motors de cerca.

Malgrat que han passat més de sis anys des de l'arribada de la tecnologia, i durant aquest temps molta aigua ha volat realment sota el pont al món frontal, per a molts desenvolupadors aquesta idea encara està envoltada en secret. No ens vam deixar de banda i vam llançar una aplicació Vue amb suport SSR a la producció, faltant un petit detall: no vam enviar l'estat inicial al client.

Per què? No hi ha una resposta exacta a aquesta pregunta. O no volien augmentar la mida de la resposta del servidor, o per un munt d'altres problemes arquitectònics, o simplement no es va enlairar. D'una manera o altra, llençar l'estat i reutilitzar tot el que va fer el servidor sembla bastant adequat i útil. La tasca és realment trivial - l'estat simplement s'injecta al context d'execució, i Vue l'afegeix automàticament al disseny generat com a variable global: window.__INITIAL_STATE__.

Un dels problemes que ha sorgit és la incapacitat de convertir estructures cícliques en JSON (referència circular); es va resoldre simplement substituint aquestes estructures per les seves contraparts planes.

A més, quan tracteu contingut UGC, heu de recordar que les dades s'han de convertir a entitats HTML per no trencar l'HTML. Per a aquests propòsits fem servir he.

Minimització dels redibuixos

Com podeu veure al diagrama anterior, en el nostre cas, una instància de Node JS realitza dues funcions: SSR i "proxy" a l'API, on es produeix l'autorització de l'usuari. Aquesta circumstància fa que sigui impossible autoritzar mentre el codi JS s'executa al servidor, ja que el node és d'un sol fil i la funció SSR és sincrònica. És a dir, el servidor simplement no pot enviar-se sol·licituds a si mateix mentre la pila de trucades està ocupada amb alguna cosa. Va resultar que vam actualitzar l'estat, però la interfície no va deixar de moure's, ja que les dades del client s'havien d'actualitzar tenint en compte la sessió de l'usuari. Hem d'ensenyar a la nostra aplicació a posar les dades correctes en l'estat inicial, tenint en compte l'inici de sessió de l'usuari.

Només hi havia dues solucions al problema:

  • adjuntar dades d'autorització a sol·licituds entre servidors;
  • dividiu les capes de Node JS en dues instàncies separades.

La primera solució requeria l'ús de variables globals al servidor, i la segona ampliava el termini per completar la tasca com a mínim un mes.

Com fer una elecció? Habr sovint es mou pel camí de menor resistència. De manera informal, hi ha un desig general de reduir al mínim el cicle de la idea al prototip. El model d'actitud cap al producte recorda una mica els postulats de booking.com, amb l'única diferència que Habr es pren molt més seriosament els comentaris dels usuaris i confia en tu, com a desenvolupador, per prendre aquestes decisions.

Seguint aquesta lògica i el meu propi desig de resoldre ràpidament el problema, vaig triar variables globals. I, com passa sovint, els has de pagar tard o d'hora. Vam pagar gairebé immediatament: vam treballar el cap de setmana, vam aclarir les conseqüències, vam escriure Post mortem i va començar a dividir el servidor en dues parts. L'error va ser molt estúpid, i l'error que el va implicar no va ser fàcil de reproduir. I sí, és una llàstima per això, però d'una manera o altra, ensopegant i gemegant, el meu PoC amb variables globals, tanmateix, va entrar en producció i està funcionant amb força èxit mentre esperava el pas a una nova arquitectura de "dos nodes". Aquest va ser un pas important, perquè formalment es va aconseguir l'objectiu: SSR va aprendre a oferir una pàgina completament llesta per utilitzar i la interfície d'usuari es va calmar molt.

Registres de desenvolupadors de front-end Habr: refactorització i reflexióInterfície Mobile Habr després de la primera etapa de refactorització

En definitiva, l'arquitectura SSR-CSR de la versió mòbil condueix a aquesta imatge:

Registres de desenvolupadors de front-end Habr: refactorització i reflexióCircuit SSR-CSR de "dos nodes". L'API Node JS sempre està preparada per a E/S asíncrona i no està bloquejada per la funció SSR, ja que aquesta es troba en una instància independent. La cadena de consulta número 3 no és necessària.

Eliminació de sol·licituds duplicades

Després de realitzar les manipulacions, la representació inicial de la pàgina ja no va provocar epilèpsia. Però l'ús posterior de Habr en mode SPA encara va causar confusió.

Atès que la base del flux d'usuaris són les transicions del formulari llista d'articles → article → comentaris i viceversa, era important optimitzar el consum de recursos d'aquesta cadena en primer lloc.

Registres de desenvolupadors de front-end Habr: refactorització i reflexióTornar al feed de publicacions provoca una nova sol·licitud de dades

No calia cavar a fons. A la pantalla de dalt podeu veure que l'aplicació torna a sol·licitar la llista d'articles quan feu lliscar cap enrere i durant la sol·licitud no veiem els articles, la qual cosa significa que les dades anteriors desapareixen en algun lloc. Sembla que el component llista d'articles utilitza un estat local i el perd en destruir-lo. De fet, l'aplicació utilitzava un estat global, però l'arquitectura Vuex es va construir frontalment: els mòduls estan lligats a pàgines, que al seu torn estan lligades a rutes. A més, tots els mòduls són "d'un sol ús": cada visita posterior a la pàgina va reescriure tot el mòdul:

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

En total, teníem un mòdul Llista d'articles, que conté objectes de tipus Article i mòdul PàginaArticle, que era una versió ampliada de l'objecte Article, tipus de Article complet. En general, aquesta implementació no porta res terrible en si mateixa: és molt senzill, fins i tot es podria dir ingenu, però extremadament comprensible. Si reinicieu el mòdul cada vegada que canvieu la ruta, fins i tot podreu viure amb ell. Tanmateix, moure's entre fonts d'articles, per exemple /feed → /tots, està garantit per llençar tot allò relacionat amb el feed personal, ja que només en tenim un Llista d'articles, a la qual cal posar dades noves. Això ens porta de nou a la duplicació de peticions.

Després d'haver recollit tot el que vaig poder desenterrar sobre el tema, vaig formular una nova estructura estatal i la vaig presentar als meus companys. Les discussions van ser llargues, però al final els arguments a favor van superar els dubtes, i vaig començar la implementació.

La lògica d'una solució es revela millor en dos passos. Primer intentem desacoblar el mòdul Vuex de les pàgines i lligar directament a les rutes. Sí, hi haurà una mica més de dades a la botiga, els getters seran una mica més complexos, però no carregarem els articles dues vegades. Per a la versió mòbil, aquest és potser l'argument més fort. Es veurà com això:

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

Però què passa si les llistes d'articles es poden solapar entre diverses rutes i què passa si volem reutilitzar les dades d'objectes? Article per renderitzar la pàgina de publicació, convertint-la en Article complet? En aquest cas, seria més lògic utilitzar aquesta estructura:

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

Llista d'articles aquí només és una mena de repositori d'articles. Tots els articles que s'han baixat durant la sessió d'usuari. Els tractem amb la màxima cura, perquè es tracta d'un trànsit que es pot haver descarregat per dolor en algun lloc del metro entre estacions, i definitivament no volem tornar a causar aquest dolor a l'usuari obligant-lo a carregar dades que ja té. descarregat. Un objecte ArticlesIds és simplement una matriu d'identificacions (com si "enllacés") a objectes Article. Aquesta estructura permet evitar duplicar dades comunes a les rutes i reutilitzar l'objecte Article en renderitzar una pàgina de publicació combinant-hi dades ampliades.

La sortida de la llista d'articles també s'ha tornat més transparent: el component iterador itera a través de la matriu amb els ID d'article i dibuixa el component teaser de l'article, passant l'Id com a complement, i el component fill, al seu torn, recupera les dades necessàries de Llista d'articles. Quan aneu a la pàgina de publicació, obtenim la data ja existent de Llista d'articles, fem una sol·licitud per obtenir les dades que falten i simplement les afegim a l'objecte existent.

Per què és millor aquest enfocament? Com he escrit més amunt, aquest enfocament és més suau pel que fa a les dades descarregades i us permet reutilitzar-les. Però, a més d'això, obre el camí a algunes possibilitats noves que encaixen perfectament en aquesta arquitectura. Per exemple, enquestar i carregar articles al feed tal com apareixen. Simplement podem posar les darreres publicacions en un "emmagatzematge" Llista d'articles, deseu una llista separada de nous identificadors ArticlesIds i notificar-ho a l'usuari. Quan fem clic al botó "Mostra noves publicacions", simplement inserirem nous identificadors al començament de la matriu de la llista actual d'articles i tot funcionarà gairebé de manera màgica.

Fes que la descàrrega sigui més agradable

La cirereta del pastís de refactorització és el concepte d'esquelets, que fa que el procés de descàrrega de contingut a Internet lenta sigui una mica menys fàstic. No hi va haver discussions sobre aquest tema; el camí de la idea al prototip va durar literalment dues hores. El disseny pràcticament es va dibuixar per si mateix i vam ensenyar als nostres components a renderitzar blocs div senzills i amb prou feines parpellejant mentre esperem dades. Subjectivament, aquest enfocament de la càrrega redueix realment la quantitat d'hormones de l'estrès al cos de l'usuari. L'esquelet té aquest aspecte:

Registres de desenvolupadors de front-end Habr: refactorització i reflexió
Habraloading

Reflexionant

Fa sis mesos que treballo a Habré i els meus amics encara em pregunten: bé, com us sembla allà? D'acord, còmode, sí. Però hi ha alguna cosa que fa que aquesta obra sigui diferent d'altres. Vaig treballar en equips totalment indiferents al seu producte, que no sabien ni entenien qui eren els seus usuaris. Però aquí tot és diferent. Aquí et sents responsable del que fas. En el procés de desenvolupament d'una funció, us convertireu parcialment en el seu propietari, participeu en totes les reunions de productes relacionades amb la vostra funcionalitat, feu suggeriments i preneu decisions. Fer un producte que utilitzeu cada dia és molt interessant, però escriure codi per a persones que probablement ho són millors que vosaltres és una sensació increïble (sense sarcasme).

Després de la publicació de tots aquests canvis, vam rebre comentaris positius i va ser molt, molt agradable. És inspirador. Gràcies! Escriu més.

Us recordo que després de les variables globals vam decidir canviar l'arquitectura i assignar la capa de proxy en una instància separada. L'arquitectura de "dos nodes" ja ha arribat al llançament en forma de proves beta públiques. Ara qualsevol pot canviar-hi i ajudar-nos a millorar Habr mòbil. Això és tot per avui. Estaré encantat de respondre a totes les vostres preguntes als comentaris.

Font: www.habr.com

Afegeix comentari