Привет, на связи Павел Востриков, архитектор веб-направления в «Лаборатории Касперского». Сегодня я расскажу про User Interface Framework (UIF) — нашу внутреннюю платформу интеграции веб-приложений, которая позволяет проводить разработку микрофронтов и микросервисов разными командами, делает удобным переиспользование кода и увеличивает гибкость подхода, чтобы разные команды могли варьировать технологии под свои нужды.

image

Мы начали разрабатывать UIF еще в 2016 году, когда само понятие Micro-Frontends только входило в обиход. Платформа родилась из-за отсутствия на рынке готовых инструментов. А со временем стала одним из наших самых эффективных решений, существенно сократив нескольким продуктам time-to-market и стоимость разработки, и даже научилась автогенерировать UI!

Эта непростая диаграмма


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

  1. Нужно быстро и недорого создавать новые продукты.
  2. Нужно сохранять для этих продуктов единый стиль UI/UX.
  3. Нужно легко интегрировать веб-приложения друг с другом.

Мы создавали платформу, чтобы их решить. И вот чего добились. Так выглядит разработанное с помощью UIF приложение:

image
image

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

Однако саму UIF минималистичной не назовешь.

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

UIF делится на две сферы: low-code и code-based. Первая включает тулсет, не требующий кода, интерактивные формы и документацию. Во второй содержатся компоненты, сервисы и скаффолды.

Прелести лоукодного тулсета


Начнем с лоукода. В нашем тулсете многое можно сделать, не написав ни строчки на JavaScript.

С помощью Mnemon мы прогоняем тесты без кода, просто записав пользовательские действия. Для дебага интеграционных составляющих и форм используем отдельного девтул-детектива — Colombo. А Domain CLI позволяет в пару кликов генерировать типовые сценарии-скаффолды.

Но база наших операций — UI Builder, редактор, в котором мы накидываем визуальную составляющую будущих страниц.

Здесь хранится дизайн системы и библиотека UI-компонентов — от простых атомов-кнопок до сложных «организмов» вроде стандартного пользовательского соглашения. Библиотека обширная, ее хватает на создание разнообразных страниц и приложений. А благодаря унифицированному дизайну элементов все эти страницы потом успешно вписываются в нашу экосистему.

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

Итак, сейчас у нас в приложениях несколько тысяч уникальных экранов с типовым UI. Держать их все в html-верстке и в ней же вносить изменения — трата времени, сил и денег. К тому же с таким количеством экранов у разных разработчиков обязательно появятся неоднородности в UI. Элементы будут единые, а вот расположение и назначение — разные. Поэтому мы создали фичу, уникальную для UIF: автогенерацию UI.

По сути, это интерфейс, который позволяет накидать скелет той или иной страницы в дизайнерском режиме буквально drag&drop’ом, прописав логику на JavaScript. Готовый скелет в JSON’е отправляется в ядро UIF. А на его основе автоматически создается полноценный интерфейс. UI так создавать быстрее, это уже помогло нам в сжатые сроки разработать целое семейство проектов. И таким образом неоднородностей удается избежать.

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

Кроме того, разработчикам нравится работать с кодом. А визуальные элементы у них не в фаворе. Если у них есть выбор, они всегда выберут код. Сейчас мы активно инвестируем в Developer Experience, чтобы дать разрабам возможность управлять формами из кода. Наш девелопер будет просто заходить в файл с кодом, говорить: «Мне нужно пять инпутов подряд, каждый инпут смотрит на такую модель данных, а данные отправляет сюда», — и получать необходимый результат.

Многие, наверное, задаются вопросом: а зачем вообще такой оверхед? Почему нельзя просто взять %name% и делать UI? Ну, давайте рассмотрим такой вариант, чисто гипотетически.

Итак, выбрал ты библиотеку или фреймворк, выбрал код-стайл, настроил линтинг, настроил pre-commit-хуки, сконфигурировал сборку, выбрал решение по хранению данных, выбрал решение по передаче данных, разобрался с проблемами больших файлов, транспонировал эти же решения на middle-tier, настроил горизонтальное масштабирование и связанные с этим stateless-механики, выставил bulk- и rollback-операции для сложных запросов, сформировал инфраструктуру для взаимной интеграции со смежной командой, выделил общую логику для упрощения работы в точках интеграций, настроил на ci проверку и прогон unit-тестов, smoke-e2e-тестов, on-demand-скриншотинг, научился собираться и в on-prem и в cloud, поддержал OAuth, научился работать в мультибэкендовом межсервисном режиме, сформировал integrated- и standalone-поставку, собрал фидбек со смежных команд, упростил подходы, выстроил интеграцию с дизайн-системой, поддержал темизацию, написал проверку 3pc, перевыбрал часть библиотек, вспомнил, что забыл задокументировать, задокументировал, понял, что остались еще проблемы, выстроил слой мониторинга, научился снимать тепловые карты с продукта… И остался, конечно, полностью в силах еще поддерживать проект, быстро писать business/presentation-логику и актуализировать технологии.

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

Библиотека Касперского


Последняя составляющая нашей лоукод-сферы — это монорепозиторий. Здесь лежит единая кодовая база, пайплайны и сборки. А также вся документация по UIF, ответы на частые вопросы и другая полезная информация. (За подробностями обращайтесь к нашей хабрастатье про монорепу.)

Конечно, когда мы только начинали работу с UIF, ничего подобного у нас не было. Впервые платформу задействовали в разработке Kaspersky Endpoint Security Cloud. Это облачный продукт безопасности, каждый пользователь которого работает одновременно с двумя-тремя сотнями устройств. Его разрабатывала небольшая на тот момент команда, решение было компактное.

image
image

Но когда мы подошли к следующему проекту, Kaspersky Security Center Web Console, все резко усложнилось. Это решение было рассчитано на энтерпрайз, бизнес, большое производство.

Разработкой KSC Web Console занялись четыре команды — host application и три продуктовых. Отсюда разногласия в UI, деплой по частям и другие прелести распределенной разработки.

image
image

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

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

UIF все популярнее в «Лаборатории Касперского». Сейчас платформу используют 25 продуктовых команд, а до конца года их может стать уже больше тридцати. И мы тоже на месте не стоим: например, добавляем к теории практику — скоро в монорепе появится режим песочницы. Можно будет работать с отдельными плагинами и сервисами без ограничений. Уверены, это поможет нашим разрабам находить новые пути и решения.

Заглядываем под капот


Перейдем к code-based-сфере. В 2016-м готовых решений, которые бы полностью из коробки решали эти задачи, просто не было. Технологии фронтенд-разработки — React, Vue, Angular — отлично подходили для создания отдельных приложений, но не могли обеспечить единые правила интеграции и взаимодействия различных программ внутри одного большого приложения-хоста. Webpack Module Federation еще не существовало, то есть настроить деплой микрофронтендов тоже было проблемно.

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

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

Итак, самое важное, фундамент платформы — наш стек. На фронтенде мы используем React, TypeScript, Redux + Immutable.js + адаптер для слоя форм, styled-components и D3. На бэке пользуемся Node.js, Express, socket.io, и все это контейнеризовано.

Каждое решение здесь на своем месте. Наша основная идея — чем проще это использовать в продукте, тем лучше. Почему, например, React, а не Angular, Vue или Svelte, Stencil и Lit? Потому что React так же хорош, как и остальные решения. Плюс adoption в сообществе. Плюс необходимость в выборе единого решения для унификации процессов. При этом есть продуктовые команды, которые исторически пишут на Angular, и технической возможности перейти «здесь и сейчас» на React просто нет. Решение простое и эффективное: для interop такого вида используются веб-компоненты в качестве «клея» между Angular и React.

Зачем D3 с надстройкой react-force-graph? Потому что пользователям нужны содержательные и стильные графики, которые к тому же быстро отрисовываются.

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

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

Плагины и скаффолды


Итак, каждый элемент стека занят одним из сервисов с нашей диаграммы: маршрутизацией, управлением состояниями или другой важной работой. Но как же все эти сервисы интегрируются между собой?

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

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

Наш секретный элемент


На диаграмме он не отражен, но без него UIF никогда не смогла бы работать нормально. Это, конечно, наша команда.

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

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

Кроме того, мы активно взаимодействуем с нашими не-UIF-разработчиками. Перенимаем у них экспертизу. Вовлекаем в процесс. Объясняем, как у нас все работает. Нашим командам всегда есть с кем расти.

А еще UIF — это ведь про гибкость и свободу. Когда мы отдаем задачу команде разработки, она вольна самостоятельно выбирать, какие технологии использовать в рамках контракта. К примеру, нам нужно через state management описать правила игры в большом приложении. Мы объясняем разработчикам, что нужно сделать, а уж использовать им при этом Redux или MobX — их дело.

Заключение


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

Поэтому буду рад, если вы поделитесь в комментариях своим опытом работы над платформой и интеграциями: работая над UIF, мы очень внимательно относимся к предложениям, комментариям и замечаниям :).

Комментарии (11)


  1. pfffffffffffff
    19.08.2022 21:20

    А можно ли в вашем решении легко отделить компоненты вашей компании и написать допустим свои для моей? И ваше решение опенсурс?


    1. vostrik Автор
      19.08.2022 23:22

      >> легко отделить компоненты

      Да. Так как решение модульное, построенное на Monorepo (git) и npm пакетах, при инициализации можно подключить другой модуль с UI Kit. Фактически, передать другой список компонентов в инициализируюущую функцию.

      Из вещей, требующих улучшения: сейчас есть оверхед с адаптерами для компонентов. Для условного компонента Table требуется адаптер, чтобы соединить его (компонент) со слоем DSL. Опираясь на текущий опыт, это кажется избыточным. Работаем над упрощением.

      При этом, если использовать только component driven подход, то ваша собственная библиотека компонент соединяется с UIF не дольше, чем работает npm i.

      >> Решение опенсурс

      Нет. Мы только идём в эту сторону. С точки зрения рабочих потребностей есть задачи, которые бы решились через наш open source. Но и с точки зрения инженерной культуры публикация UIF будет шагом в нужную сторону.


      1. george3
        20.08.2022 15:01

        Из вещей, требующих улучшения: сейчас есть оверхед с адаптерами для компонентов. Для условного компонента Table требуется адаптер, чтобы соединить его (компонент) со слоем DSL. Опираясь на текущий опыт, это кажется избыточным. Работаем над упрощением.

        можно посмотреть как это решено здесь https://github.com/Claus1/unigui . заодно узнать о протоколе.


        1. vostrik Автор
          20.08.2022 17:30

          Спасибо. Выглядит очень интересно.
          Также познакомился с вашими статьями Универсальный GUI ~= конец страданиям и Порт GUI фреймворка с Python на Go. Анализ граблей и плюшек.

          Из технических нюансов, которые нам важны:

          1. богатая библиотека компонентов на фронте, чтобы в совокупности с продуманными пользовательскими сценариями иметь отличный UX.

          2. возможность создавать UI без DSL, т. к. понимаем, что форму настроек можно (и на наш взгляд нужно) разрабатывать типовым образом, а вот специфичные продуктовые компоненты GeoMap или Chat эффективнее собирать руками на основе UI Kit.

          3. front-to-front взаимодействие плагинов (микрофронтов, интегрированных в host application) для обеспечения кросс-продуктовых сценариев.

          Мне понравилось, как вы сформулировали в одном из тредов под вашей статьёй:

          >> проще говоря — не думать o GUI и не программить GUI. только реакция на изменение юзером данных

          Это ровно та задача, которую мы решаем с помощью DSL. Но повторюсь, здесь, на мой взгляд, очень важно соблюсти баланс, когда DSL помогает нам в типовых операциях и не мешает в специфичных (кастомных). Именно поэтому мы считаем, что DSL применим для «параллельного и перпендикулярного UI», встречающегося сотни раз в проекте. А для всего остального есть UI Kit и component driven подход.

          P. S.

          >> Подобный GUI хорошо выглядит на простых примерах, где просто эдитки, чекбоксы и так далее. С этими «голый» HTML тоже отлично справляется

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


          1. george3
            21.08.2022 08:59

            >>вот здесь вам может потребоваться дополнительный инструментарий поверх HTML.

            не нужно это, если протокол покрывает все нужные типы данных, и устраивает единый стиль интерфейса (MD). Если на всех экранах нужен составной элемент, делается блок, кладется в blocks папку и используется во всех экранах.

            Пример:

            Вверху общий блок Parameters. Описан 1 раз в blocks.

            ```user_edit = Edit('User ID', 1)

            tape_length = Edit('Tape length', 20)

            inertia = Edit('Update inertia', 0.5)

            buffer_length = Edit('Buffer length', 2)

            setting_block = Block('Parameters', [ user_edit,

            Button("(Re)start", recalc_init, icon = "cloud_sync"), tape_length,

            inertia, buffer_length], icon = "settings" )

            ```


  1. KarabasBarabaska
    20.08.2022 18:52
    +1

    В целом по статье - словоблудие и графомания.

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

    Подозреваю, что разработчик, работая в этой штуке, постепенно забудет, как работать с остальным фронтом и станет "частью корабля, частью команды")

    Так что становится неким UIF-разработчиком ради этого - ну такое.

    По итогу, продукт является ровно тем чем он и назван в статье - большим толстым и бородатым оверхедом


    1. vostrik Автор
      20.08.2022 18:58

      Много слов ниже, это чтобы дать деталей, подсветил boldом единственный вопрос к вам.

      TL;DR:

      React 17 (едем на 18), Node.js LTS 6 лет подряд (6, 8, 10, 12, 14 и сейчас 16) и AntD не могу назвать бородатым оверхедом.

      Себя могу, а вот штуки выше — нет.

      А дальше давайте попробуем разобраться детально.

      Во-первых, большое спасибо за ваше мнение и критику.

      >> В целом по статье - словоблудие и графомания

      Скажите, пожалуйста, о чём хотелось прочитать в первую очередь, но не нашли?

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

      >> Продукту явно хорошо там где он вырос

      Да! При этом для нас основной метрикой является не хорошо ли продукту, то есть UIFу. А хорошо ли продуктам, которые разрабатываются с использованием UIF.

      А центральной метрикой является достигаются ли бизнес-потребности. Приведу только 2 примера через призму технологий:

      1. Код получается дешевле в поддержке или нет?

      2. Разработчикам проще писать сложные интеграционные сценарии?

      >> Подозреваю, что разработчик, работая в этой штуке, постепенно забудет, как работать с остальным фронтом и станет "частью корабля, частью команды")

      >> Так что становится неким UIF-разработчиком ради этого - ну такое.

      Это очень классное замечание, кмк.

      Именно поэтому весь трюк в том, что никаким UIF-разработчиком вам становится не нужно.

      UIF — это набор in-house npm пакетов, построенных вокруг Open Source, и API для интеграции веб-приложенек (читай кросс-продуктовое взаимодействие в одной вкладке).

      Давайте приведу несколько областей, входящих в состав UIF:

      1. UI Kit

        Это React, AntD, Styled Components и Storybook. Смотрим в сторону Style Dictionary.

      2. Валидация данных

        Это пакет validator + набор специфичных продуктовых правил валидации + единый интерфейс для взаимодействия (чтобы оставить платформе контроль за имплементацией)

      3. Observability

        Это OpenTelemtry: Jaeger, Prometheus и json-строки

      Поэтому у UIF нет (и не ставилось) задачи переизобрести.

      Ставилась задачи предкофигурировать и интегрировать. Плюс быстрое вкатывание в проект.

      Есть ли оверхеды? Блин, конечно да.

      Код написанный вчера — уже легаси. А если ещё и с претензией на абстракции..

      Как мы с этим работаем: учимся и внимательно слушаем. Так появился стрим Experts Committe для обсуждения и принятия решений. Описал ниже.

      >> стороннему разработчику пользы пока никакой.

      >> Пользы где то вне Касперского от этой поделки скорее всего не будет.

      Почему решили написать и в чём я вижу пользу:

      1. Рассказать о том, что у нас есть и как устроено.

      2. Получить обратную связь и внести корректировки в наши решения.

      3. Выйти в Open Source. Действительно, выложив наш UI Kit мы никого не удивим, но облегчим интеграцию с нашими продуктами — раз. И дадим ещё примеров хороших инженерных практик — два.
        А вот наша плагинная архитектура с возможностью кросс-продуктовой интеграции и low code подход для типовых UI может быть полезен и вне Kaspersky.

      Так как третье пока In Progress, я посчитал, что 1 и 2 – этого достаточно для публикации и это ровно то, что мы делаем в сообществе.

      >> По итогу, продукт является ровно тем чем он и назван в статье - большим толстым и бородатым оверхедом

      Нет, нет и ещё раз нет.

      Мы могли бы быть там. Прям все шансы имели.

      Если бы не опирались на опыт других и сами бы кое-чего не понимали в веб-разработке.

      Чтобы митигировать этот риск, очень опасный и дорогой риск уйти в сторону «своего болота», у нас есть 3 простых правила:

      1. Можешь сделать на ванильном js / ts? Сделай.

      2. Можешь сделать, используя Open Source? Сделай.

      3. Не хватает 1 и 2? Приноси PR или RFC в UIF Experts Committee. Это делегаты от продуктовых команд из разных направлений разработки. Лиды, архитекторы, старшие разработчики, пишущие на разном (JS / TS, Node.js / Go / .Net / C++, React / Angular / Vue), проведут ревью, накидают комментариев и уже потом решение попадёт в основной транк. Разумеется, ребята из гошечки или плюсеры будут смотреть RFC микросервисного шасси для Node.js и вряд ли аддон для сторибука.



      1. yroman
        20.08.2022 19:17

        Зачем давать обратную связь закрытому продукту? Вот откроетесь - тогда и поговорим. Ну есть у вас платформа какая-то, ну и замечательно. Сейчас у каждого третьего есть свой велик с квадратными колёсами, толку-то?


        1. vostrik Автор
          20.08.2022 19:40

          >> Вот откроетесь - тогда и поговорим

          Ок!


    1. i360u
      20.08.2022 20:01

      При том, что по сути я, лично, с вами полностью согласен, по форме можно было и помягче выразится. Художника обидеть может каждый. Люди стараются, делятся опытом... это не словоблудие.

      Но в целом - да, выглядит все довольно громоздко и под капотом абсолютно тривиальный стек.


      1. vostrik Автор
        20.08.2022 21:13

        >> абсолютно тривиальный стек

        Да!

        Руль на понятном месте, педали на понятном месте. Сели и поехали. Это ровно то, к чему мы стремились.

        >> выглядит все довольно громоздко

        Я понял, что нужны примеры кода, а лучше открытый репозиторий. Работаем над этим.

        Но пока попробую на тех диаграммах, которые есть под рукой.

        UIF — это буквально 3 слоя:

        Использовать можно только UI Kit, можно так же работу с формами и DSL, а можно всю плагинную архитектуру.

        И теперь давайте разберём use cases:

        1. Разработка standalone веб-приложения — create-react-app.

          Или любой другой скаффолдинг для любимого стека.

        2. Разработка микрофронтов: Module Federation или single-spa / qiankun.

        3. Разработка большого веб-приложения, состоящего из множества маленьких плагинов (представителей продуктов), которые должны между собой:

          1. взаимодействовать

          2. быть похожими друг на друга

        Вот для третьего пункта и нужен UIF.

        Таким образом, UIF, состоящий из UI Kit, слоя работы с формами и плагинной архитектуры, используется в следующих случаях:

        1. UIF-based Web App

          Веб-интерфейс продукта полностью построен на UIF, включая использование UI Kit, форм, навигации и т. д.

        2. UIF Plugin

          Продуктовый плагин, разработанный на UIF и запускающийся в Host App.

        3. Bundle Plugin (for Non-UIF Web App)

          Плагин, разработанный на UIF и запускающийся в Web App.

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

          В дальнейшем весь код Bundle Plugin, при необходимости, можно перенести в единый UI (UIF-based Web App).

        4. UI Components only

          Продукт использует только библиотеку компонентов из UIF.

          То есть продукт выглядит идентично другим решениям, но UIF не используется как интеграционная платформа.