Внутри Mailion: как устроен фронтенд почты на миллион пользователей

в 10:00, , рубрики: javascript, mailion, React, ReactJS, TypeScript, Блог компании МойОфис, микросервисы, микрофронтенды, мойофис, почта, разработка, Софт, фронтенд
Внутри Mailion: как устроен фронтенд почты на миллион пользователей - 1

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

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

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


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

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

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

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

Внутри Mailion: как устроен фронтенд почты на миллион пользователей - 2

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

Так выглядит почта 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:

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

Автор:
romanzhivo

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js