La création et la maintenance de composants communs est un processus dans lequel de nombreuses équipes doivent être engagées. Le chef du service de composants partagés de Yandex Vladimir Grinenko
tadatuta a expliqué comment leur développement dépassait l'équipe dédiée de Lego, comment nous avons créé un mono-référentiel basé sur GitHub en utilisant Lerna et mis en place des versions Canary avec la mise en œuvre de services directement dans CI, ce qui était nécessaire et ce qui était nécessaire. être encore.

"Heureux de vous accueillir tous." Mon nom est Vladimir, je fais des choses courantes dans les interfaces Yandex. Je veux en parler. Probablement, si vous n'utilisez pas nos services très profondément, vous pouvez avoir une question: que composons-nous tous? Qu'y a-t-il à composer?


Il y a une liste de réponses dans les résultats de recherche, parfois il y a une colonne à droite. Chacun de vous s'en sortira probablement en une journée. Si vous vous souvenez qu'il existe différents navigateurs et ainsi de suite, nous ajoutons un autre jour pour corriger les bugs, puis tout le monde y fera face.

Quelqu'un se rappellera qu'il existe encore une telle interface. Tenant compte de toutes les petites choses que vous pouvez lui donner une autre semaine et allez-y. Et Dima vient de nous dire qu'il y a tellement d'entre nous que nous avons besoin de notre propre école. Et tous ces gens composent des pages tout le temps. Chaque jour, ils viennent travailler et composer, imaginez? Il y a clairement autre chose.

En fait, les services de Yandex sont bien plus. Et il y a même un peu plus que sur cette diapositive. Derrière chaque lien, il y a un tas d'interfaces différentes avec une grande variabilité. Ils sont pour différents appareils, dans différentes langues. Ils travaillent parfois même dans des voitures et d'autres choses étranges.

Aujourd'hui, Yandex n'est pas seulement le Web, pas seulement des marchandises différentes avec des entrepôts, la livraison et tout ça. Voitures jaunes roulent. Et pas seulement ce que vous pouvez manger, et pas seulement des morceaux de fer. Et pas seulement toutes sortes d'intelligences automatiques. Mais tout ce qui précède est uni par le fait que pour chaque élément, des interfaces sont nécessaires. Souvent - très riche. Yandex est des centaines de services énormes différents. Nous créons constamment quelque chose de nouveau chaque jour. Nous comptons des milliers d'employés, dont des centaines de développeurs front-end et de développeurs d'interfaces. Ces personnes travaillent dans différents bureaux, vivent dans des fuseaux horaires différents, de nouveaux types viennent constamment travailler.
Dans le même temps, nous, dans la mesure où nous avons suffisamment de force, essayons de le rendre monotone, uniforme pour les utilisateurs.

Il s'agit de la recherche de documents sur Internet. Mais si nous passons à l'émission d'images, l'en-tête correspond, malgré le fait qu'il s'agit d'un référentiel séparé, qui est engagé dans une équipe complètement distincte, peut-être même sur d'autres technologies. Il semblerait qu'il y ait compliqué? Eh bien, ils ont fait un chapeau deux fois, comme une chose simple. Chaque bouton de la casquette a également son propre monde intérieur riche et distinct. Quelques popups apparaissent ici, quelque chose peut également y être poussé. Tout cela est traduit dans différentes langues, fonctionne sur différentes plateformes. Et ici on passe des photos, par exemple, à la vidéo, et c'est encore une fois un nouveau service, une autre équipe. Encore un autre référentiel. Mais toujours le même chapeau, bien qu'il y ait des différences. Et tout cela doit rester uniforme.
Qu'est-ce que ça vaut, de basculer comme ça sur les diapositives, pour être sûr que rien ne soit allé nulle part sur le pixel? Nous essayons d'empêcher que cela se produise.

Pour montrer l'échelle un peu plus, j'ai pris une capture d'écran du référentiel, qui ne stocke que le code frontal pour les nouveaux navigateurs - uniquement la sortie des documents, sans images ni vidéos. Il y a des dizaines de milliers de commits et près de 400 contributeurs. Ce n'est que dans la mise en page, un seul projet. Voici une liste de liens bleus que vous avez l'habitude de voir.
Sergey Berezhnoy, mon chef, aime beaucoup cette histoire, puisque nous nous sommes tellement rassemblés dans l'entreprise, je veux que notre interaction fonctionne ensemble comme si en JavaScript: un plus un est plus de deux.

Et nous essayons d'obtenir tout ce que nous pouvons de l'interaction. La première chose qui vient à l'esprit dans de telles conditions est la réutilisation. Ici, par exemple, un extrait vidéo sur un service dans les résultats de recherche pour une vidéo. Il s'agit d'une sorte de photo avec une signature et d'autres éléments différents.

Si vous regardez plus loin, voici la délivrance habituelle des documents. Mais ici aussi, il y a exactement le même extrait.

Ou, disons, il existe un service Yandex.Air distinct, qui se compose un peu moins que complètement d'extraits similaires.

Ou, disons, l'extrait vidéo dans le notifiant, qui se trouve sur différentes pages du portail.

Ou voici un extrait de vidéo lorsque vous l'ajoutez à vos favoris, puis le regardez dans vos collections.

Sonne comme? Évidemment, semble-t-il. Et alors? Si nous permettons vraiment aux services d'intégrer facilement nos composants finis dans d'autres services de portail, alors évidemment ce service, du fait que les utilisateurs peuvent interagir avec leurs données sur différentes plateformes, obtiendra plus d'utilisateurs. C'est super. Les utilisateurs en bénéficieront également. Ils verront les mêmes choses également. Ils se comporteront comme d'habitude. Autrement dit, il n'est pas nécessaire de deviner encore et encore ce que le concepteur avait en tête ici et comment interagir avec lui.
Et enfin, l'entreprise en tirera des économies évidentes. De plus, il semble juste - qu'y a-t-il pour constituer un aperçu vidéo et une sorte de signature / En fait, pour l'obtenir comme ça, vous devez mener beaucoup d'expériences différentes, tester différentes hypothèses, choisir les tailles, les couleurs, les retraits. Ajoutez des éléments, peut-être, puis supprimez-les, car ils n'ont pas volé. Et ce qui s'est passé, ce qui fonctionne vraiment, est le résultat d'un très long processus. Et si à chaque fois dans chaque nouvel endroit de le faire à nouveau, c'est un énorme effort.
Imaginez maintenant. Disons que nous avons quelque chose qui fonctionne bien. Partout, partout, ils l'ont mis en œuvre, puis ils ont mené une nouvelle expérience et réalisé ce qui pouvait être amélioré. Et encore une fois, nous devons répéter toute cette chaîne de mise en œuvre. Cher

D'accord, il semble évident de bien réutiliser. Mais maintenant, nous devons résoudre un certain nombre de nouveaux problèmes. Vous devez comprendre où stocker ce nouveau code. D'une part, cela semble logique. Ici nous avons un extrait vidéo, il est réalisé par l'équipe vidéo, ils ont un référentiel avec leur projet. Il faudrait probablement y mettre. Mais comment alors le distribuer à d'autres référentiels de tous les autres gars? Et si d'autres gars veulent apporter quelque chose à eux dans cet extrait? Encore une fois pas clair.
Il est nécessaire de le versionner d'une manière ou d'une autre. Vous ne pouvez rien changer, et donc, le tour est joué, tout se déroule soudainement. Quelque chose doit être testé. De plus, nous l'avons par exemple testé sur le service de la vidéo elle-même. Mais que se passe-t-il si, lorsqu'il est intégré à un autre service, quelque chose se casse? Encore une fois pas clair.
En fin de compte, il est en quelque sorte nécessaire d'assurer en quelque sorte une livraison assez rapide à différents services, car ce sera étrange si nous avons quelque part la version précédente, quelque part de nouveau. L'utilisateur semble cliquer sur la même chose et le comportement est différent. Et nous devons en quelque sorte offrir aux développeurs de différentes équipes la possibilité d'apporter des modifications à ce code commun. Nous devons en quelque sorte leur apprendre à tout utiliser. Nous avons un long chemin à parcourir pour que la réutilisation des interfaces soit pratique.

Nous avons commencé dans des temps immémoriaux, de retour dans SVN, et c'était comme une lampe et pratique: un papa avec HTML, tout comme dans Bootstrap. Vous le copiez pour vous. À côté de papa avec des styles, une sorte de JS là-bas, qui a ensuite su simplement montrer / cacher quelque chose. Et c'est tout.

D'une certaine manière, la liste des composants ressemblait à ceci. Le b-domeg, qui était responsable de l'autorisation, est mis en évidence ici. Peut-être vous souvenez-vous encore, sur Yandex, en effet, il y avait un tel formulaire de connexion et de mot de passe, avec un toit. Nous avons appelé la "maison", même si elle faisait allusion à l'enveloppe du courrier, car ils entraient généralement dans le courrier.
Ensuite, nous avons mis au point une méthodologie complète pour pouvoir prendre en charge des interfaces communes.

La bibliothèque elle-même au sein de l'entreprise a acquis son propre site Web avec une recherche et toute taxonomie.

Le référentiel ressemble maintenant à ceci. Vous voyez, aussi près de 10 000 commits et plus de 100 contributeurs.

Mais c'est le dossier de la maison très b dans la nouvelle réincarnation. Maintenant, elle ressemble à ça. Il y a déjà plus de vos propres dossiers à l'intérieur que la moitié d'un écran.

Et donc le site a l'air aujourd'hui.

En conséquence, la bibliothèque partagée est utilisée dans plus de 360 référentiels à l'intérieur de Yandex. Et il y a différentes implémentations, un cycle de mise au point débogué, etc. Il semblerait qu'ici, nous avons une bibliothèque commune, utilisons-la maintenant partout, et tout est super. Le problème de l'introduction de trucs communs partout résolu. Pas vraiment.

Essayer de résoudre le problème de la réutilisation au stade où vous avez déjà du code prêt à l'emploi est trop tard. Cela signifie qu'à partir du moment où le concepteur a dessiné la disposition des services, les a distribués aux services, et en particulier à l'équipe qui s'occupe des composants communs, un certain temps s'est écoulé. Très probablement, à ce moment-là, il se révélera que sur chaque service distinct, ou au moins sur plusieurs d'entre eux, ce même élément d'interface a également été constitué. Ils se sont arrangés à leur manière.
Et même si une solution générale est apparue plus tard dans la bibliothèque partagée, il s'avère que vous devrez maintenant réimplémenter tout ce que vous avez réussi à terminer sur chaque service. Et c'est encore un problème. C'est très difficile à justifier. Voici l'équipe. Elle a ses propres objectifs, tout fonctionne déjà bien. Et nous disons de telles choses - regardez, enfin nous avons une petite chose commune, prenez-la. Mais l'équipe est comme ça - nous avons déjà assez de travail. Pourquoi en avons-nous besoin? D'ailleurs, tout à coup quelque chose ne nous conviendra pas là-bas? Nous ne voulons pas.
Le deuxième gros problème est, en fait, la diffusion d'informations sur ce que sont ces nouveaux composants sympas. Tout simplement parce qu'il y a tellement de développeurs, ils sont occupés avec leurs tâches quotidiennes. Et ils ont la possibilité de s'asseoir et d'étudier ce qui se passe là-bas dans le domaine du commun, peu importe ce que cela signifie, en fait, non plus.
Et le plus gros problème est qu'il est fondamentalement impossible de résoudre les problèmes communs à tous les services avec une seule équipe dédiée. Autrement dit, lorsque nous avons une équipe qui s'occupe de la vidéo et qu'elle crée son propre extrait avec la vidéo, il est clair que nous serons d'accord avec eux et ferons cet extrait dans une sorte de bibliothèque centralisée. Mais il existe directement des milliers d'exemples de ce type sur différents services. Et ici, certainement, aucune main ne suffit. Par conséquent, la seule solution est que tout le monde traite constamment les composants généraux.

Et vous devez commencer, assez étrangement, non pas avec des développeurs d'interfaces, mais avec des concepteurs. Ils le comprennent aussi. Nous avons plusieurs tentatives simultanées à l'intérieur pour que ce processus converge. Les concepteurs créent des systèmes de conception. J'espère vraiment que tôt ou tard il sera possible de les réduire en un seul système commun prenant en compte tous les besoins.

Maintenant, il y en a plusieurs. Étonnamment, les tâches sont exactement les mêmes: accélérer le processus de développement, résoudre le problème de cohérence, ne pas réinventer la roue et ne pas dupliquer le travail effectué.

Et une façon de résoudre le problème de la communication d'informations est de permettre aux développeurs de connaître d'autres équipes, dont une qui s'occupe des composants d'interface courants. Nous le résolvons de ce côté par le fait que nous avons un bootcamp, qui, lorsqu'un développeur apparaît sur Yandex, lui permet tout d'abord d'aller dans différentes équipes pendant huit semaines, voir comment ça marche, puis faire un choix où ça va marcher . Mais pendant ce temps, ses horizons s'élargiront considérablement. Il sera guidé là où c'est.
Nous avons parlé de choses communes. Voyons maintenant comment tout cela se rapproche du processus de développement. Disons que nous avons une bibliothèque commune appelée Lego. Et nous voulons implémenter une nouvelle fonctionnalité ou faire une sorte de révision. Nous avons corrigé le code et publié la version.
Nous devons publier cette version dans npm, puis aller dans le référentiel d'un projet où la bibliothèque est utilisée et implémenter cette version. Très probablement, cela corrigera un certain nombre dans package.json, redémarrez l'assembly. Peut-être même régénérer le package-lock, créer une demande d'extraction, voir comment les tests réussissent. Et que verrons-nous?

Très probablement, nous verrons qu'un bogue s'est produit. Parce qu'il est très difficile de prévoir toutes les façons d'utiliser le composant sur différents services. Et si cela arrivait, quelle est notre issue? Nous avons donc réalisé que cela ne s'emboîtait pas. Nous continuons à refaire. Nous retournons au référentiel avec une bibliothèque partagée, corrigeons le bogue, publions la nouvelle version, l'envoyons à npm, déployons, exécutons les tests, et qu'est-ce que c'est? Très probablement, un bug se reproduira.
Et c'est toujours bon quand nous l'implémentons dans un seul service, et là, tout s'est cassé tout de suite. C'était beaucoup plus triste quand nous avons fait tout cela, mis en œuvre dans dix services différents. Rien ne s'est cassé là. Nous sommes déjà allés préparer un smoothie, ou tout ce qui est nécessaire. À l'heure actuelle, la version est introduite dans le 11e projet, ou dans le 25e. Et il y a un bug. Nous revenons tout au long de la chaîne, faisons un patch et l'implémentons dans les 20 services précédents. De plus, ce patch peut exploser dans l'un des précédents. Eh bien et ainsi de suite. Amusez-vous.
Il semble que la seule solution consiste à écrire beaucoup de code très rapidement. Tôt ou tard, si nous courons très, très vite, très probablement, nous parviendrons à avoir le temps de mettre en production une version dans laquelle il n'y a pas encore de bug. Mais alors une nouvelle fonctionnalité apparaîtra, et rien ne nous sauvera.
D'accord. En fait, le schéma peut ressembler à ce qui suit. L'automatisation nous aidera. Il s'agit de cela, en général, de toute l'histoire, en fait. Nous avons eu l'idée qu'un référentiel avec une bibliothèque commune puisse être construit selon le schéma de mono-référentiel. Vous l'avez probablement rencontré, il existe maintenant de nombreux projets de ce type, en particulier des projets d'infrastructure. Toutes sortes de Babel, et des choses comme ça, vivent comme des mono-référentiels quand il y a beaucoup de packages npm différents à l'intérieur. Ils peuvent être en quelque sorte connectés les uns aux autres. Et ils sont gérés, par exemple, via Lerna, de sorte qu'il est pratique de publier tout cela, compte tenu des dépendances.
Selon ce schéma, il est possible d'organiser un projet où tout en commun est stocké pour toute l'entreprise. Il peut y avoir une bibliothèque, qui est engagée dans une équipe distincte. Et, notamment, il peut y avoir des packages que chaque service individuel développe, mais qu'il souhaite partager avec d'autres.

Ensuite, le circuit ressemble à ceci. Le début n'est pas différent. D'une manière ou d'une autre, nous devons apporter des modifications au code commun. Et puis, avec l'aide de l'automatisation, d'un seul coup, nous voulons exécuter des tests non seulement à côté de ce code, mais immédiatement dans tous les projets où ce code commun est intégré. Et voyez leur résultat agrégé.
Ensuite, même si un bogue s'est produit là-bas, nous n'avons toujours pas réussi à publier de version, nous ne l'avons pas publiée dans les npm, nous ne l'avons pas implémentée spécifiquement de nos mains, nous n'avons pas fait tous ces efforts supplémentaires. Nous avons vu un bogue, l'avons immédiatement corrigé localement, avons de nouveau exécuté des tests généraux, et c'est tout en production.

À quoi cela ressemble-t-il dans la pratique? Voici une demande d'extraction avec un correctif. Ici, vous pouvez voir que l'automatisation a appelé les réviseurs nécessaires pour vérifier que tout va bien dans le code. En effet, les examinateurs sont venus et ont convenu que tout allait bien. Et à ce moment, le développeur écrit simplement une commande spéciale / canary, directement dans la demande de tirage.
Un robot vient et dit - d'accord, j'ai créé une tâche pour que le prochain miracle se produise. Le miracle est qu'une version canari avec ces modifications a été publiée et qu'elle a été implémentée automatiquement dans tous les référentiels où ce composant est utilisé. Des autotests y ont été lancés, comme dans ce référentiel. Ici, vous pouvez voir qu'un tas de contrôles ont été lancés.
Derrière chaque test, il y a une centaine de tests différents. Mais il est important qu'en plus des tests locaux que nous pourrions écrire séparément sur le composant, nous avons également lancé des tests pour chaque projet où il a été implémenté. Des tests d'intégration y ont déjà été lancés: on vérifie que ce composant fonctionne normalement dans l'environnement dans lequel il est conçu sur le service. Cela nous garantit déjà vraiment que nous n'avons rien oublié, que nous n'avons rien cassé à personne. Si tout va bien ici, alors nous pouvons vraiment publier une version en toute sécurité. Et si quelque chose ne va pas ici, nous allons le réparer ici.
Il semble que cela devrait nous aider. Si votre entreprise a quelque chose de similaire, vous voyez qu'il y a des pièces que vous pourriez potentiellement réutiliser, mais pour l'instant vous devez les réorganiser car il n'y a pas d'automatisation, je vous recommande de trouver une solution similaire.

Qu'avons-nous obtenu? Le monorepositaire général dans lequel les linters sont reconstruits. Autrement dit, tout le monde écrit le code de la même manière, il a toutes sortes de tests. Toute équipe peut venir, mettre son composant et le vérifier avec des tests unitaires JS, le couvrir de captures d'écran, etc. Tout sera déjà sorti de la boîte. L'examen de code intelligent que j'ai mentionné. Grâce à de riches outils internes, c'est vraiment intelligent ici.
Le développeur est-il en vacances maintenant? L'appeler dans une demande de pull est inutile; le système en tiendra compte. Le développeur est-il malade? Le système en tiendra également compte. Si les deux conditions ne sont pas remplies et que le développeur semble être libre, il recevra une notification dans l'un de ses messagers de son choix. Et il est comme ça: non, maintenant je suis occupé par quelque chose d'urgence ou lors d'une réunion. Il peut y venir et simplement écrire la commande / busy. Le système comprendra automatiquement que vous devez attribuer le suivant à la liste.
L'étape suivante consiste à publier la même version canari. Autrement dit, avec tout changement de code, nous devons publier un ensemble de services que nous pouvons vérifier sur différents services. Ensuite, nous devons exécuter des tests lors du déploiement sur tous ces services. Et quand tout s'est réuni - lancez les versions.
Si une modification affecte une partie statique qui doit être chargée à partir du CDN, vous devez la publier automatiquement séparément. . , , , , . , , , changelog - .
, , , , . , , .
, . . , , : , ? . , . . , .