Недавно мы представили защищенную корпоративную почтовую систему «Mailion. Сертифицированный» — единственную на российском рынке с действующим сертификатом ФСТЭК России. Продукт предназначен для работы с конфиденциальной информацией в крупных коммерческих и государственных организациях.

Речь о сложно устроенной и технологически разнообразной системе: Mailion включает в себя семь крупных модулей, более 400 собственных компонентов (не считая стилевых, вспомогательных и интеграционных обвязок), и содержит в целом почти 400 тыс. строк кода.

Под катом — наш рассказ об устройстве пользовательской части Mailion. Говорим об архитектуре фронтенда и о том, как и почему менялся его стек с начала разработки в 2017 году.


Привет, Хабр! Меня зовут Роман Животягин, более восьми лет я разрабатываю софт: специализируюсь на JavaScript и TypeScript, владею PHP и C#. В МойОфис руковожу одной из команд разработки Mailion.

На Хабре о создании этой почтовой системы уже писали мои коллеги — советую их статьи (1, 2, 3, 4, 5, 6) в первую очередь тем, кому интересно базовое устройство продукта, его предназначение, бэкенд и тема микросервисов. В этом же материале я расскажу о технологическом пути, который мы с командой Mailion прошли за последние годы в разработке и совершенствовании «фронта».

Из чего состоит Mailion: архитектура и стек

Архитектуру Mailion верхнего уровня можно представить в виде иерархической модульной структуры. Точкой входа в нее служит приложение-оболочка (shell) с общей функциональностью: хранилища, менеджер состояний, общие обработчики и пр. К оболочке подключаются относительно независимые модули, которые содержат собственную функциональность и состоят из переиспользуемых компонентов. Всего модулей семь: профиль пользователя, почта, календарь, администрирование, контакты, справка и настройки. В комплексе они покрывают большинство потребностей, связанных с коммуникациями в крупных компаниях.

Вот как некоторые составляющие системы выглядят со стороны пользователя:

Так выглядит почта c открытой медиа-панелью, которая показывает участников одной ветки переписки и прикрепленные ими файлы
Так выглядит почта c открытой медиа-панелью, которая показывает участников одной ветки переписки и прикрепленные ими файлы
Календарь на неделю с событиями двух сотрудников
Календарь на неделю с событиями двух сотрудников
Интерфейс администрирования позволяет гибко управлять пользователями, в том числе их правами доступа и принадлежностью к группам
Интерфейс администрирования позволяет гибко управлять пользователями, в том числе их правами доступа и принадлежностью к группам

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

На старте проекта в 2017 году мы выбрали в качестве фреймворка библиотеку Polymer. Основной причиной стало то, что Polymer собрал под капотом различные преимущества веб-компонентов — в виде удобного API и целостной библиотеки, готовой к использованию. К тому же казалось, что Google планирует активно продвигать Polymer в качестве нового стандарта (какое-то время, судя по всему, так оно и было). Но прежде чем продолжить рассказ о фреймворке, предлагаю поговорить о специфике самих веб-компонентов.

Зачем нужен компонентный подход?

Когда я начинал свой путь в разработке, компонентный подход во фронтенде только зарождался. Компании начали имплементировать MVC в виде библиотек и фреймворков, только-только появились AngularJS, Backbone и прочие инструменты. Но зачастую при знакомстве с новыми технологиями бывает вообще непонятно, как с этим работать. Распространенный подход того времени: у нас есть кучка маленьких библиотек, кучка библиотек побольше, есть jQuery, берём всё это и пишем «фронт». При этом HTML по большей части собирался на стороне сервера (то, что сегодня возродилось в концепции SSR), на клиенте же требовалось добавить интерактивности или сделать ajax-формочки.

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

  • Пользовательские элементы (custom elements). Позволяют нам регистрировать собственные компоненты: создавать их, привязывать к ним некое поведение, стилизовать их, произвольно именовать, описывать их программную и визуальную часть, инкапсулируя их внутри компонента.

  • Теневая DOM (shadow DOM). После регистрации кастомных компонентов, мы можем привязать к ним теневую модель. Она позволяет нам изолированно от основной DOM работать со стилями, разметкой и кодом того или иного компонента.

  • HTML-шаблоны (спецификации <template>, <slot>). <template> дает возможность отложенной генерации какого-то представления; <slot> же позволяет создать в модели документа врезку, в которой компонент существует независимо от основной части документа.

Теперь вернемся к Polymer, который собрал под капотом все эти технологии, и рассмотрим его ключевые концепции:

  • Миксины. Функция, которая принимает некий базовый класс и возвращает расширенный класс. Пользоваться ей мы стали не сразу, поскольку начинали с Polymer 1.0, в то время как миксины появились в версии 2.0.

  • Поведение. Благодаря миксинам мы можем наследовать функциональность и задавать определенное поведение, которое компоненты могут потом перенаследовать и переиспользовать.

  • Инкапсуляция. Программный код, стили и разметка содержатся в одном компоненте.

  • Ленивая загрузка. Актуальна для больших приложений вроде Mailion, поскольку позволяет пользователю не загружать данные, которые он пока не будет использовать.

  • Шаблон наблюдателя. Вычисляемое свойство, когда мы можем на основе некоторых переменных вычислить другую переменную в одной функции.

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

В ходе работы мы выявили следующие недостатки и преимущества Polymer:

Плюсы

Минусы

— Простой и понятный API

— Все плюшки веб‑компонентов

— Простое переиспользование кода

— Набор компонентов из коробки

— Скудная поддержка сообществом

— Не всегда очевидное наследование и переопределение стилей

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

— Сложность кастомизации сторонних компонентов

Приходим к микрофронтендам

При разработке Mailion мы остановились на структуре типа «монолит»: есть Polymer и есть монолитный репозиторий, в котором мы хранили компоненты. Постепенно мы наращивали логику, компонентов и подприложений становилось все больше, а затем какой-то момент Google объявила, что прекращает развивать Polymer.

Поскольку у нас уже была большая кодовая база, мы решили рассмотреть другие варианты фреймворков и библиотек. При этом «копали» в сторону взаимодействия фреймворков: например, проводили исследования, как Vue или React будут взаимодействовать с тем же Polymer. В результате поняли, что нам нужны микрофронтенды. Для миграции решили выбрать монорепозиторий с управлением под системой сборки Nx, плюс React и TypeScript.

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

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

Микрофронты предполагают набор важных для нашей разработки возможностей:

  1. Разделение больших модулей на независимые микрофронтенды.

  2. Упрощение масштабирования приложений при увеличении численности команды разработки.

  3. Можно выпускать релизы отдельными частями приложения = >> повышение частоты релизов.

  4. Увеличение скорости автотестирования.

  5. Разворачивание микрофронта на отдельном стенде.

  6. Можно взбалтывать разные стеки, но не смешивать их (гибридное приложение). Мы, например, можем использовать Polymer в оболочке на React без опасений, что что-то будет работать не так.

Основные варианты, на чем можно делать микрофронты:

  • Ванильный JS.

  • Специализированные библиотеки и фреймворки.

  • Мультирепозитории.

  • Монорепозитории.

  • Федерация модулей webpack — когда вы можете разделить код и при этом разместить его на разных хостах.

Наши варианты — монорепозитории и федерация модулей.

Почему мы выбрали React в качестве библиотеки?

Изначально подкупили эти преимущества:

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

  • Большая поддержка сообщества.

  • Множество сопутствующих библиотек.

  • Интеграция с Nx по умолчанию.

Сразу скажу о возможностях Nx — довольно молодой системы сборки, которая решает для нас ряд проблем:

  • Разделение на проекты «из коробки». В терминологии Nx все является проектами. Есть проекты приложения и библиотеки, мы можем их хранить и разрабатывать отдельно, при необходимости можем их публиковать, нам проще их поддерживать.

  • Независимая сборка приложений.

  • Ограничение взаимозависимости проектов через механизм тегирования областей.

  • Генераторы рабочего пространства.

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

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

Чем полезно разделение кода

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

Федерация модулей позволяет нам делать очень важную вещь. Мы с самого начала можем запланировать конфигурацию таким образом, что разделим отдельные части — например, оболочку приложения, приложение 1 и приложения 2 — запустим их на разных хостах, и оболочка будет тянуть эти приложения по сети. Мы можем расположить приложения на разных хостах и работать с ними изолированно. Это и есть реализация идеи микрофронтов.

Еще раз о стеке

В заключение остановлюсь подробнее на составе нашего стека технологий. Точнее — стеков, поскольку мы ведем разработку и на Polymer, и на React.

Polymer 2.0

React

— Веб‑компоненты

— Redux

— IndexedDB

— Service Worker

— WebSockets

— History API

— Bower

— Electron

— Nx

— TypeScript

— React Query

— IndexedDB

— Service Worker

— WebSockets

— Electron

— History API

— MUI

— Storybook

— Jest

— Cypress

— husky

— Webpack

— Yarn

В перспективе мы нацелены на создание гибридного приложения, и пока что гибрид подняли частично. Так, в оболочку на React у нас уже мигрирована часть компонентов: например, настройки, лейауты, сайдбары и календарь. Отдельным приложением на React реализована авторизация.

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

Что касается Mailion на Polymer 2.0 — это пример прогрессивного приложения, где под капотом есть все:

  • Мы используем подход offline first, то есть при отсутствии сети ходим в первую очередь в локальную базу данных (это удобно, например, когда пользователь хочет почитать цепочку писем).

  • Поскольку мы реализуем поддержку миллиона пользователей, а отдавать статику на миллион — не очень хорошо, мы кэшируем через Service Worker те вещи, которые не надо загружать по сети.

  • WebSockets задействуем для нотификаций, History API — для роутинга.

  • Наконец, для настольной версии почты на популярных системах (macOS, Windows, Linux) мы используем Electron.

***

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

Много интересного вы можете почерпнуть и в других хабр-статьях про разработку Mailion:

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

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


  1. Lolman
    00.00.0000 00:00
    +3

    Придет время и React падёт смертью храбрых по воле FB или из-за моды на новый фреймворк. Также как и судьба Nx не в наших руках. На своем опыте сталкивался с этой проблемой, проект начинали на Ext.JS (умер), продолжили на AngularJS (умер) и сейчас перешли на Vue (скорее всего, умирает). Каждый переход был осознанным выбором и не казался настолько временным.

    Стартапам эта поблема не страшна, они часто делают pivot'ы или быстро гибнут сами. Но для крупных проектов этот переход обходится в полугодовой бюджет разработки, при этом вводятся моратории на новый функционал, много новых багов и прочие прелести перехода на новые технологии. А если бизнес не потратит деньги на переход, то остается у разбитого корыта, с которым большинство разработчиков не умеют или не хотят связываться. То есть, проект обречен на медленную мучительную смерть и держится на дедах, как в тех историях про COBOL, только это происходит прямо сейчас с web-технологями, которые были "на коне" всего-то 5-10 лет назад.

    Правильно ли я понимаю, вы решили эту проблему временно при помощи Nx, вам не нужно избавляться от Polymer прямо сейчас, но вам все равно придется это сделать, поскольку Polymer не поддерживается и в какой-то момент превратится в тыкву?

    Какие есть способы действительно обезопасить себя от гипер-изменчвого мира фронтенда, чтобы не тратить деньги бизнеса на переход на новые библиотеки или фреймворк каждые 3 года? Или это новая норма: теперь надо заранее планировать переписывание проекта на новые технологии раз в 3-5 лет?

    https://gist.github.com/tkrotoff/b1caa4c3a185629299ec234d2314e190


    1. romanzhivo Автор
      00.00.0000 00:00

      Правильно ли я понимаю, вы решили эту проблему временно при помощи Nx, вам не нужно избавляться от Polymer прямо сейчас, но вам все равно придется это сделать, поскольку Polymer не поддерживается и в какой-то момент превратится в тыкву?

      Да, всё верно, мы прямо сейчас не избавляемся от Polymer, в планах у нас в первую очередь гибрид - часть компонентов и подприложений мы уже перевели на React, часть будут крутиться на Polymer. Однако вероятность того, что Polymer совсем превратится в тыкву даже без поддержки, довольно низкая с той точки зрения, что Polymer это всё же в некотором роде обёртка над нативно поддерживаемыми технологиями веб-компонентов.

      Какие есть способы действительно обезопасить себя от гипер-изменчвого мира фронтенда, чтобы не тратить деньги бизнеса на переход на новые библиотеки или фреймворк каждые 3 года? Или это новая норма: теперь надо заранее планировать переписывание проекта на новые технологии раз в 3-5 лет?

      Сложно сказать :) Мир JS, и фронтенда в частности, всегда был довольно динамичный и изменчивый. Моя личная точка зрения - не стоит гнаться за библиотеками и фреймворками, гораздо важнее понимать общие принципы функционирования языка и подходов к проектированию. Любая библиотека/фреймворк это всё же функция от возможностей самого языка/базовой технологии.


      1. ArtemGolovin96
        00.00.0000 00:00
        +1

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


    1. Heggi
      00.00.0000 00:00
      +1

      Почему вы считаете что Vue умирает?
      Со своей стороны я никак не могу ни подтвердить, ни опровергнуть это утверждение, да и вообще нигде не встречал подобного мнения.
      От этого еще больше интересно ваше мнение


      1. Lolman
        00.00.0000 00:00
        +1

        Можно судить по опросам разработчиков, трендам Stack Overflow, по советам которые разработчики дают другим новичкам

        % would use again у VUE снижается, у React растет
        % would use again у VUE снижается, у React растет

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

        Конечно возможно, что Vue останется на плаву. Но тренд на реактизацию интернета беспощаден.


        1. Heggi
          00.00.0000 00:00
          +1

          Если посмотреть на те же тренды SO, то увидим, что vuejs падает, но при этом растет vuejs3, причем, навскидку, падение vuejs за 2022 год отыграно ростом vuejs3.

          То что реакт значительнее популярнее vue - факт, но это не значит что vue умирает.

          Вот Angular, судя по графикам, умирает, ну как минимум теряет популярность.


          1. Lolman
            00.00.0000 00:00
            +1

            Я лишь предполагаю. У Vue (вместе взятых 1+2+3) сейчас максимальная историческая популярность, но рост популярности прекратился, мы находимся на плато, если не будет некого "рывка" со стороны комьюнити, то она пойдет на спад.

            Если просто смотреть исторически, вероятнее всего очередной фреймворк вымерет, прежде чем мы достигнем осознанности в веб-разработке и гонка "у кого JS моднее" прекратится. Поэтому считаю что и React забудут лет через 10.


            1. romanzhivo Автор
              00.00.0000 00:00
              +1

              Согласен)

              Или же произойдёт то, что происходило с Ангуляром версий 1.x и 2.x+, или и Полимером версий 1.x и 3.x - они станут просто несовместимы


    1. noodles
      00.00.0000 00:00
      +1

      Какие есть способы действительно обезопасить себя от гипер-изменчвого мира фронтенда, чтобы не тратить деньги бизнеса на переход на новые библиотеки или фреймворк каждые 3 года?

      как вариант - по умолчанию выделять 20% ресурсов разработки на рефакторинг.. на протяжении всего срока жизни проекта, т.е. всегда.


  1. Pan_Stephan
    00.00.0000 00:00
    +2

    отличная статья об интересном и сложном продукте!