Общие компоненты силами разных команд. Доклад Яндекса

Создание и сопровождение общих компонентов — процесс, в котором должны быть заняты множество команд. Руководитель службы общих компонентов Яндекса Владимир Гриненко tadatuta объяснил, как их разработка переросла выделенную команду «Лего», как мы сделали монорепозиторий на базе GitHub с помощью Lerna и настроили Canary-релизы с внедрением в сервисы прямо в CI, что для этого понадобилось, а что ещё предстоит.

zvo-0b7pajia8d3pnm4rbpm72hw.jpeg

— Рад вас всех приветствовать. Меня зовут Владимир, я занимаюсь общими штуками в интерфейсах Яндекса. Про них и хочу поговорить. Наверное, если вы не очень глубоко пользуетесь нашими сервисами, у вас может возникнуть вопрос: что мы все верстаем? Что там верстать?

o3ipsxj2wxuhbnfeq23rfk0ucuc.jpeg

wqkxy0_ta9qyzi3vnbryhxqpwy4.jpeg

Есть какой-то список ответов в поисковой выдаче, иногда еще справа колонка. Каждый из вас наверняка за день справится. Если вспомнить, что есть разные браузеры и так далее, то добавим еще день на исправления багов, и тогда уже точно каждый справится.

gcf5i8mqymopdqairky09odo00k.jpeg

Кто-то вспомнит, что есть еще такой интерфейс. На него с учетом всяких мелочей можно дать еще неделю и на этом разойтись. А Дима только что рассказывал, что нас так много, что нам нужна целая своя школа. И все эти люди все время верстают странички. Каждый день приходят на работу и верстают, представляете? Явно есть что-то еще.

iv3detsvcvujieb_-ho8nqmvvuu.jpeg

На самом деле сервисов в Яндексе, действительно, поболее. И их даже чуть поболее, чем на этом слайде. За каждой такой ссылкой стоит куча разных интерфейсов с огромной вариативностью. Они есть для разных устройств, на разных языках. Они работают иногда даже в автомобилях и прочих разных странных вещах.

0wmz98pcjwflkkhflu4i_vwgtjo.jpeg

Яндекс сегодняшний — это не только веб, не только разные товары со складами, доставкой и всем таким. Машинки желтые ездят. И даже не только то, что можно съесть, и не только железяки. И не только всякие автоматические интеллекты. Но все перечисленное объединяет то, что для каждого пункта нужны интерфейсы. Зачастую — очень богатые. Яндекс — это сотни разных огромных сервисов. Мы постоянно создаем что-то новое, каждый день. У нас трудятся тысячи сотрудников, среди которых сотни именно фронтендеров, разработчиков интерфейсов. Эти люди работают в разных офисах, живут в разных часовых поясах, на работу постоянно приходят новые ребята.

При этом мы, насколько нам хватает сил, стараемся, чтобы он был однообразен, един для пользователей.

dzvnuzxrodwkt2oi0tbemkn4lne.gif

Это выдача поиска по документам в интернете. Но если мы перейдем на выдачу по картинкам — шапка совпадает, несмотря на то, что это отдельный репозиторий, которым занимается абсолютно отдельная команда, возможно, даже на других технологиях. Казалось бы, что там сложного? Ну сверстали два раза шапку, вроде дело нехитрое. За каждой кнопочкой в шапке тоже свой отдельный богатый внутренний мир. Тут появляются какие-то попапы, там тоже что-то можно понажимать. Все это переводится на разные языки, работает на разных платформах. И вот мы переходим с картинок, например, на видео, и это снова новый сервис, другая команда. Опять другой репозиторий. Но все равно та же шапка, хотя есть отличия. И все это нужно оставить единообразным.

Чего стоит, переключаясь вот так по слайдам, убедиться, что ничего никуда на пиксель не съехало? Мы стараемся, чтобы этого не происходило.

_u9b2ralx_p1qvev6q_k1jsr7ys.jpeg

Чтобы еще чуть-чуть показать масштаб, я взял скриншот репозитория, который хранит только код фронтенда для новых браузеров — только выдачи по документам, без картинок и видео. Тут десятки тысяч коммитов и почти 400 контрибьюторов. Это только в верстке, только один проект. Вот список синих ссылок, который вы привыкли видеть.

Сергей Бережной, мой руководитель, очень любит эту историю, что раз уж нас так много собралось в компании — хочется, чтобы от нашего взаимодействия суммарно получалось как в JavaScript: один плюс один больше, чем два.

qqkn-qr0bqavfyeum_ocif9gycs.jpeg

И мы пытаемся получить все что можно от взаимодействия. Первое, что приходит в голову в таких условиях, — повторное использование. Вот, например, сниппет видео на сервисе в поисковой выдаче по видео. Это какая-то картинка с подписью и еще какие-то разные элементы.

j5rjprkjwcplnephmm-dxciv1l4.jpeg

Если смотреть дальше, то вот обычная выдача по документам. Но здесь тоже есть точно такой же сниппет.

cr3kt4p_iys_uplw7yi7ltsntn4.jpeg

Или, вот, допустим, есть отдельный сервис Яндекс.Эфир, который чуть менее чем полностью состоит из похожих сниппетов.

qt91iipkwlqktjjqpnwhmfxapzs.jpeg

Или, допустим, сниппет видео в нотификаторе, который есть на разных страницах портала.

azw92ih1mulgzijr1gdgbenx8k0.jpeg

Или вот сниппет видео, когда вы добавляете его в ваше Избранное, а потом смотрите его в ваших Коллекциях.

ubgeydeso0lwyy4t67ejiz7uh6w.jpeg

Похоже? Очевидно, похоже. И что? Если мы действительно позволим сервисам свои готовые компоненты легко внедрить на другие сервисы портала, то, очевидно, этот сервис, за счет того, что пользователи смогут взаимодействовать с их данными на разных площадках, получит больше пользователей. Это здорово. Пользователи от этого тоже выиграют. Они будут видеть одинаковые штучки одинаково. Они будут вести себя привычно. То есть не придется каждый раз заново угадывать, что тут имел в виду дизайнер, и как с этим взаимодействовать.

И, наконец, компания от этого получит очевидную экономию. Причем, это только кажется — чего там верстать превьюшку видео и какую-то подпись/ На самом деле чтобы она получилась именно такой, нужно провести уйму разных экспериментов, проверить разные гипотезы, подобрать размеры, цвета, отступы. Какие-то элементы добавить, возможно, потом убрать, потому что они не полетели. И то, что получилось, то, что действительно работает, — результат очень длительного процесса. И если каждый раз в каждом новом месте делать его заново, это огромное количество усилий.

А теперь представьте. Допустим, мы получили что-то, хорошо работающее. Везде-везде внедрили, а потом провели новый эксперимент и поняли, что можно еще улучшить. И опять нам придется повторить всю эту цепочку внедрения. Дорого.

ex8c1_tlclflyej-krijb8m20fk.jpeg

О'кей, вроде очевидно, переиспользовать хорошо. Но теперь нам приходится решить целый ряд новых вопросов. Нужно понять, где хранить такой новый код. С одной стороны, вроде бы, логично. Вот у нас есть сниппет видео, его делает команда видео, у них есть репозиторий с их проектом. Наверное, надо положить туда. Но как его потом распространить в остальные репозитории всех других ребят? А если другие ребята захотят что-то свое привнести в этот сниппет? Опять не понятно.

Нужно как-то это версионировать. Нельзя что-то поменять, и чтобы оно, вуаля, всем внезапно раскатилось. Нужно как-то тестировать. Причем, мы, допустим, протестировали это на сервисе самого видео. Но что, если при интеграции в другой сервис что-то сломается? Опять не понятно.

Нужно как-то, в конце концов, обеспечить достаточно быструю доставку на разные сервисы, потому что будет странно, если у нас где-то предыдущая версия, где-то новая. Пользователь вроде кликает на одно и то же, а там разное поведение. И нам нужно как-то обеспечить возможность разработчикам разных команд вносить изменения в такой общий код. Нужно как-то научить их всем этим пользоваться. У нас достаточно долгий путь к тому, чтобы повторное использование интерфейсов стало удобным.

gua8ensfq4qkfjd9hwgrf3dpeko.jpeg

Начинали мы в незапамятные времена, еще в SVN, и было лампово и удобно: папочка с HTML, прямо как в Bootstrap. Вы его копипастите к себе. Рядом папочка со стилями, какой-то там JS, который тогда умел что-то просто показать/скрыть. И все.

tgxusxowszje9vwtdvxf4yy4_os.jpeg

Как-то так выглядел список компонентов. Здесь подсвечен b-domeg, который отвечал за авторизацию. Возможно, вы еще помните, на Яндексе, действительно, была такая форма для логина и пароля, с крышей. Мы называли «домик», хотя она намекала на почтовый конверт, потому что входили, обычно, в почту.

Потом мы придумали целую методологию, чтобы иметь возможность поддерживать общие интерфейсы.

tu7h08reemfp5mrp8rib5a3ib9q.jpeg

Сама библиотека внутри компании обзавелась собственным сайтом с поиском и всякой таксономией.

fvpajnegfm13z-q1fzb1edglvn0.jpeg

Репозиторий сейчас выглядит вот так. Видите, тоже почти 10 тысяч коммитов и более 100 контрибьюторов.

h29ruri7jkqnzacvu9dedijcjgu.jpeg

А вот это папка того самого b-домика в новой реинкарнации. Сейчас она выглядит вот так. Там внутри уже собственных папок больше, чем на пол-экрана.

luu962u_j91qtinyg3gn-1-efwg.jpeg

А так выглядит сайт на сегодняшний день.

xjkog-2xy19nuayhuliti4urgoy.jpeg

В итоге, общая библиотека используется в более чем 360 репозиториях внутри Яндекса. И есть разные реализации, отлаженный релизный цикл и т. д. Казалось бы, вот, получили общую библиотеку, давайте ее теперь везде использовать, и все здорово. Проблема внедрения общих штучек куда угодно решена. На самом деле нет.

rje50_8ba6axvribhiogqzzxer8.jpeg

Пытаться решить проблему повторного использования на этапе, когда у вас уже есть готовый сверстанный код, слишком поздно. Это означает, что от момента, когда дизайнер нарисовал макеты сервисов, раздал их в сервисы, и, в том числе, в команду, которая занимается общими компонентами, уже прошло какое-то время. Скорее всего, к этому времени окажется так, что на каждом отдельном сервисе, или хотя бы на нескольких из них, этот же элемент интерфейса тоже сверстали. Сверстали как-то по-своему.

И даже если потом в общей библиотеке появилось общее решение, все равно окажется так, что вам придется теперь заново перевнедрять все то, что успели понаверстать на каждом сервисе. И это опять проблема. Очень сложно обосновать. Вот сидит команда. У нее есть свои цели, все уже хорошо работает. И мы такие говорим — смотрите, наконец-то у нас есть общая штучка, возьмите ее. А команда такая — у нас и так работы хватает. Зачем нам? Тем более, вдруг там что-то не будет подходить нам? Не хотим.

Вторая большая проблема — это, на самом деле, распространение информации о том, что есть эти клевые новые компоненты. Просто потому, что очень много разработчиков, они заняты своими ежедневными задачами. И у них возможности сидеть и изучать, что там происходит в районе общего, что бы это ни значило, на самом деле, тоже нет.

И самая большая проблема, это то, что принципиально невозможно одной какой-то специально выделенной командой решить проблемы общего для всех-всех сервисов. То есть, когда у нас есть команда, которая занимается видео, и она делает свой сниппет с видео, понятно, что мы с ними договоримся и сделаем этот сниппет в какой-то централизованной библиотеке. Но таких примеров на разных сервисах встречается прямо тысячи. И тут уж точно никаких рук не хватит. Поэтому единственное решение — общими компонентами должны все время заниматься вообще все.

y7ay9pqgaxomogw-xz4x7hdlgb4.jpeg

И начать нужно, как ни странно, не с разработчиков интерфейсов, а с дизайнеров. Они это тоже понимают. У нас внутри происходит несколько одновременных попыток, чтобы этот процесс сошелся. Дизайнеры делают дизайн-системы. Я очень надеюсь, что рано или поздно получится свести их в единую общую систему, которая учитывает все потребности.

bevdyd-kpziztd5yxc6qp98xgqy.jpeg

Сейчас их несколько. Как ни удивительно, задачи там стоят ровно такие же: чтобы ускорить процесс разработки, чтобы решить проблему консистентности, не изобретать заново велосипеды и не дублировать проделанную работу.

0zxdcimwdz-s1d8oaiu3dinhxce.jpeg

А один из способов решить проблему донесения информации — позволить разработчикам познакомиться с другими командами, включая ту, которая занимается общими компонентами интерфейсов. Мы ее с этой стороны решаем тем, что у нас появился Буткемп, который при появлении разработчика в Яндексе первым делом позволяет ему в течение восьми недель походить в разные команды, посмотреть, как там что устроено, и потом сделать выбор, где он будет работать окончательно. Но за это время у него заметно расширится кругозор. Он будет ориентироваться, где что есть.

Про общие вещи поговорили. Давайте теперь посмотрим, как это все выглядит уже ближе к процессу разработки. Допустим, у нас есть общая библиотека, которая называется Лего. И мы в ней хотим реализовать какую-то новую фичу или внести какую-то правку. Мы поправили код и выпустили версию.

Эту версию нам нужно опубликовать в npm, а дальше пойти в репозиторий какого-то проекта, где используется библиотека, и внедрить эту версию. Скорее всего, это поправить какую-то чиселку в package.json, перезапустить сборку. Возможно, еще перегенерировать package-lock, создать pull request, посмотреть, как пройдут тесты. И что мы увидим?

riftdzizz3nhvcxu4ivi1gtnzxy.jpeg

Скорее всего, мы увидим, что случился баг. Потому что очень сложно предугадать все способы использования компонента на разных сервисах. И если так и произошло, то какой у нас выход? Вот мы осознали, что не сошлось. Продолжаем переделывать. Возвращаемся в репозиторий с общей библиотекой, чиним баг, выпускаем новую версию, отправляем ее в npm, внедряем, запускаем тесты, и что там? Скорее всего, снова случится баг.

Причем это еще хорошо, когда мы внедряем в один сервис, и прямо там сразу все сломалось. Гораздо печальнее, когда мы все это проделали, внедрили в десять разных сервисов. Там ничего не сломалось. Мы уже пошли заваривать смузи, или что там нужно. В это время версия внедряется в 11-й проект, или в 25-й. И там находится баг. Мы возвращаемся по всей цепочке, делаем патч и внедряем его во все предыдущие 20 сервисов. Причем этот patch может взорваться в каком-то из предыдущих. Ну и так далее. Весело.

Выход, кажется, единственный — нужно просто очень быстро писать много кода. Тогда рано или поздно, если мы очень-очень быстро побежим, скорее всего, у нас получится успеть выкатить в продакшен версию, в которой бага еще нет. Но потом появится новая фича, и нас ничто не спасет.

Ладно. На самом деле, схема может быть примерно следующей. Нам поможет автоматизация. Именно об этом, в общем-то, весь рассказ, на самом деле. Мы придумали, что репозиторий с общей библиотекой может быть построен по схеме монорепозиториев. Вы наверняка сталкивались, сейчас много таких проектов, особенно инфраструктурных. Всякие Babel, и тому подобные штуки, живут, как монорепозитории, когда внутри лежит много разных npm-пакетов. Они, возможно, связаны как-то между собой. И управляются, например, через Lerna, чтобы все это удобно паблишить, учитывая зависимости.

Ровно по такой схеме можно организовать и проект, где хранится все общее, для всей компании. Там может быть и библиотека, которой занимается отдельная команда. И, в том числе, там могут быть пакеты, которые разрабатывает каждый отдельный сервис, но которыми он хочет делиться с другими.

qbtxckwz4xeknxszl3zkfftpkjo.jpeg

Тогда схема выглядит так. Начало не отличается. Нам так или иначе нужно внести правки в общий код. А потом с помощью автоматизации мы одним махом хотим запустить тесты не только рядом с этим кодом, но сразу во всех тех проектах, куда внедрен этот общий код. И увидеть их агрегированный результат.

Тогда, даже если там случился баг, то мы еще не успели выпустить никакую версию, не опубликовали ее ни в каком npm, никуда специально не внедрили руками, не проделали всех этих лишних усилий. Мы увидели баг, тут же локально починили, опять запустили общие тесты, и все, в продакшен.

lo8-7d5hg38lvxpsswq05l2vipm.jpeg

Как это выглядит на практике? Вот пулл-реквест с исправлением. Здесь видно, что автоматика призвала туда нужных ревьюверов, чтобы они проверили, что в коде все хорошо. Действительно, ревьюверы пришли и договорились, что все хорошо. И в этот момент разработчик просто пишет специальную команду /canary, прямо в пулл-реквесте.

Приходит робот и говорит — окей, я создал задачу на то, чтобы случилось следующее чудо. Чудо в том, что теперь выпустилась canary-версия с этими изменениями и она автоматически была внедрена во все репозитории, где используется этот компонент. Там запустились автотесты, как и в этом репозитории. Здесь видно, что запустилась целая куча проверок.

За каждой проверкой может стоять еще по сотне разных тестов. Но важно, что помимо локальных тестов, которые мы смогли написать на компонент отдельно, мы еще запустили тесты на каждый проект, куда она внедрилась. Там уже запустились интеграционные тесты: мы проверяем, что этот компонент нормально работает в окружении, в котором он задуман на сервисе. Это уже и правда нам гарантирует, что мы ничего не забыли, ничего никому не сломали. Если здесь все хорошо, то мы правда можем смело выпускать версию. А если здесь что-то плохо, мы здесь же и поправим.

Похоже, что нам должно это помочь. Если в вашей компании нечто похожее, вы видите, что есть части, которые вы потенциально могли бы переиспользовать, но пока вам приходится их верстать заново, поскольку нет автоматизации, — рекомендую прийти к похожему решению.

luvpjp0a5tof1v56lwn7uq_nwiq.jpeg

Что мы получили? Общий монорепозиторий, в котором отстроены линтеры. То есть все пишут код одинаково, в нем есть все виды тестов. Любая команда может прийти, положить свой компонент и проверить его JS unit-тестами, покрыть его скриншотами и т. д. Все уже будет из коробки. Умное код-ревью, которое я уже упоминал. Оно у нас благодаря богатым внутренним инструментам и правда умное.

Разработчик сейчас в отпуске? Звать его в пулл-реквест бессмысленно, система это учтет. Разработчик заболел? Система это тоже учтет. Если оба условия не выполнились и разработчик, кажется, свободен — ему прилетит уведомление в какой-то из его мессенджеров, по его выбору. И он такой: нет, сейчас я занят чем-то срочным или на встрече. Он может прийти туда и просто написать команду /busy. Система автоматически поймет, что нужно назначить следующего из списка.

Следующий шаг — публикация той самой canary-версии. То есть нам с любым изменением кода нужно выпустить служебный пакет, который мы сможем проверить на разных сервисах. Дальше нам нужно запустить тесты при внедрении на все эти сервисы. А когда все сошлось — запустить релизы.

Если в изменении затронута какая-то статика, которая должна загружаться с CDN, нужно ее автоматически опубликовать отдельно. Это тоже работает из коробки. При выпуске релиза нам, конечно, нужно знать, что происходило, что менялось. Но так как мы хотим, чтобы все это было автоматически, нужно, чтобы changelog был сформирован автоматом и где-то публиковался.

И наконец, если все разные сервисы разнесли свои компоненты, положили в отдельное общее место, то удобно построить витрину со списком всех компонентов, со всей документацией. Когда новый разработчик или дизайнер захочет понять, что уже есть, он спокойно воспользуется этим единым реестром.

Это практически счастье, но на самом деле понятен и следующий шаг. Это общий монорепозиторий для всех проектов сразу. Вы, наверное, можете спросить:, а зачем вы так страдали и придумывали столько сложной инфраструктуры, если можно было сразу сложить все вместе? Ответ простой. Если мы и правда сложим все наши проекты вместе, то никакая из доступных сейчас систем контроля версий с этим не справится. Так что нам придется изобрести подходящую. На этом у меня все, спасибо.

© Habrahabr.ru