Микросервисный подход в веб-разработке: micro frontends

9225

Микросервисный подход в веб-разработке: micro frontends

В этой статье поговорим о микросервисном подходе в веб-разработке пользовательских интерфейсов.

Типичное веб-приложение состоит из HTML-верстки, CSS-стилей и JavaScript-кода, который позволяет достичь максимального уровня интерактивности и отзывчивости. Чем выше сложность приложения, тем сложнее пользовательский интерфейс, а вследствие этого — и инструменты, которые нужны для его разработки. Именно поэтому фронтенд-разработка превратилась из простого набора дополнений для пользовательского интерфейса в сложную экосистему с большим количеством инструментов и высоким порогом входа.

Микросервисный подход в веб-разработке: micro frontends
Микросервисный подход в веб-разработке: micro frontends

Проблема и решение

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

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

  • время разработки в связи с высоким уровнем сложности кода;
  • время тестирования;
  • временной интервал между релизами.

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

Монолитная архитектура —  это архитектурный подход, в котором вся основная логика приложения собрана в одном месте. Монолитное приложение состоит из однослойного объединения разных компонент в одно целое.

Разработка новой функциональности в таком приложении, где большой объем устаревшего кода, с каждым годом становится все дороже для бизнеса. Со схожей проблемой уже сталкивались бэкенд-разработчики. Одно из решений большинства проблем монолитной архитектуры получило название «микросервисы» (Microservices).

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

Помимо этого, можно выделить и другие достоинства микросервисной архитектуры в сравнении с монолитной:

  • модульность;
  • уменьшение времени на тестирование;
  • сокращение времени на деплой и возможность делать это параллельно;
  • возможность горизонтального масштабирования команды.

А можно ли использовать микросервисный подход в веб-разработке пользовательских интерфейсов? Ответ: да! Можно и нужно! Именно такой подход и называют микрофронтенд (micro frontends).

Микрофронтенд (Micro frontends)

Микрофронтенд (micro frontend) —  архитектурный подход, в котором независимые приложения собраны в одно большое приложение. Он дает возможность объединить в одном приложении разные виджеты или страницы, написанные разными командами с использованием разных фреймворков (см. рис. ниже).

Главные преимущества микрофронтенд-подхода — в разработке больших энтерпрайз-приложений:

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

Помимо очевидных достоинств такого подхода, у него есть и существенные недостатки:

  • увеличение общей сложности приложения;
  • дублирование кода. Каждое приложение разрабатывается отдельной командой, которая принимает свои технические решения. Это ведет к повторной загрузке одинаковых фреймворков, библиотек и общему дублированию кода, который мог быть использованным повторно;
  • JS-бандл монолитного приложения всегда будет меньше, чем совокупность бандлов в микрофронтенд-архитектуре;
  • возможные проблемы с кешированием и версионностью приложений;
  • глобальные переменные или CSS-стили — это вещи, о которых стоит забыть в микрофронтенд-архитектуре, если приложения полностью не изолированы.

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

К сожалению, пока не существует конкретной спецификации для построения микрофронтенд-архитектуры. Вот, пожалуй, самые доступные и простые способы и техники для построения микрофронтенд-приложений:

  • IFrames;
  • библиотека Tailor.js;
  • фреймворк single-spa.

Пройдемся по каждому из перечисленных подходов.

IFrames

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

  • производительность;
  • сложность поддержки.

Крайне не рекомендую строить микрофронтенд-архитектуру, используя IFrame. Сегодня существуют другие способы сделать это проще и эффективней.

Библиотека Tailor.js

Здесь вы можете прочитать больше о самой библиотеке. Компания Zalando создала целую экосистему для построения микрофронтенд-архитектуры, и Tailor.js —  это часть экосистемы. Особенность Tailor.js — то, что это пакет для Node.js, и ориентирован он на построение микрофронтенд-архитектуры с серверным рендерингом.

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

Single-spa

Основной и, по моему мнению, лучший подход в построении микрофронтенд-архитектуры — это фреймворк single-spa. Вот основные причины, по которым я советую выбрать single-spa:

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

Single-spa —  это фреймворк, который дает возможность объединить разные приложения, независимо от используемой библиотеки или фреймворка, в одно целое. Под капотом single-spa набор существующих инструментов вместе с собственными решениями:

  • SystemJS —  загрузчик модулей, который нужен для асинхронной загрузки отдельных приложений;
  • врапперы —  single-spa предоставляет отдельные врапперы под каждый фреймворк, который создает обертку над приложением, нужную для интеграции и подключения отдельного приложения в общее single-spa;
  • API — single-spa предоставляет набор инструментов, которые нужны для коммуникации между отдельными приложениями, подписку на события и т. д.

Типичное приложение с использованием single-spa выглядит так:

А вот так выглядит коммуникация между отдельными элементами микрофронтенд-архитектуры, построенной с использованием single-spa:

Root Application —  это корень приложения. Именно здесь происходит подключение sigle-spa как основного фреймворка, а также конфигурация SystemJS для корректной загрузки внешних приложений.

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

Построить набор приложений с использованием микрофронтенд-подхода и single-spa можно как путем создания полностью всей инфраструктуры и приложений с нуля, так и на основе существующего приложения. Рассмотрим примеры того, как это выглядит, создавая набор полностью новых приложений с использованием React.js и Angular 8.

Конфигурация билда для React.js под single-spa представлена здесь.

import React from 'react'
import ReactDOM from 'react-dom'
import singleSpaReact from 'single-spa-react'
import { property } from 'lodash'
import setPublicPath from './set-public-path.js'
const reactLifecycles = singleSpaReact({
 React,
 ReactDOM,
 loadRootComponent: () => import(/* webpackChunkName: "react-app" */'./App.js').then(property('default')),
 domElementGetter,
})
export const bootstrap = [
 () => {
   return setPublicPath()
 },
 reactLifecycles.bootstrap,
]
export const mount = [
 reactLifecycles.mount,
]
export const unmount = [
 reactLifecycles.unmount,
]
export const unload = [
 reactLifecycles.unload,
]
function domElementGetter() {
 let el = document.getElementById("react-app");
 if (!el) {
   el = document.createElement('div');
   el.id = 'react-app';
   document.body.appendChild(el);
 }
 return el;
}

Используя существующий враппер single-spa для React.js, мы создаем интерфейс с методами bootstrap, mount, unmount, где соответственно описываем, как должно бутстрэппиться наше приложение. Враппер помогает инкапсулировать внутреннюю имплементацию и создать API для правильного подключения в single-spa фреймворка.

Похожим образом это выглядит и для Angular 8.

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { Router } from '@angular/router';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import singleSpaAngular from 'single-spa-angular';
import { singleSpaPropsSubject } from './single-spa/single-spa-props';
if (environment.production) {
 enableProdMode();
}
const lifecycles = singleSpaAngular({
 bootstrapFunction: singleSpaProps => {
   singleSpaPropsSubject.next(singleSpaProps);
   return platformBrowserDynamic().bootstrapModule(AppModule);
 },
 template: '<app-root />',
 Router,
 NgZone: NgZone,
});
export const bootstrap = lifecycles.bootstrap;
export const mount = lifecycles.mount;
export const unmount = lifecycles.unmount;

Так же, как и в случае с React.js, мы предоставляем интерфейс для ручного бутстрэппинга приложения на Angular 8.

Кроме использования специфичных врапперов, приложение должно быть собрано как amd-модуль. Каждый такой модуль асинхронно подключается в корень всей микрофронтенд-архитектуры — Root Application. Ниже пример элементарной имплементации.

<!DOCTYPE html>
<html>
 <head>
   <title>Root application</title>
   <meta name="importmap-type" content="systemjs-importmap">
   <script type="systemjs-importmap">
     {
       "imports": {
         "angular-app": "http://localhost:4201/main.js",
         "react-app": "http://localhost:4202/main.js",
         "single-spa": "https://unpkg.com/[email protected]/lib/system/single-spa.min.js"
       }
     }
   </script>
   <!-- Loading of required libraries shouyld be added -->
 </head>
 <body>
     <header class="header-fixed">
           <nav>
             <a href="/angular-app">Angular app</a>
             <a href="/react-app">React app</a>
           </nav>       
       </header>
   <script>
     System.import('single-spa').then(function (singleSpa) {
       singleSpa.registerApplication(
         'angular-app',
         () => System.import('angular-app'),
         location => location.pathname.startsWith('/angular-app')
       singleSpa.registerApplication(
         'react-app',
         () => System.import('react-app'),
         location => location.pathname.startsWith('/react-app')
       )
       singleSpa.start();
     })
   </script>
 </body>
</html>

Две главные части, на которые стоит обратить внимание:

  • script-тег, в котором нужно описать маппинг названия приложения, на адрес, с которого single-spa будет подгружать amd-модуль для этого приложения;
  • script-тег, где нужно непосредственно зарегистрировать приложение с помощью метода registerApplication. Здесь нужно указать адрес, при переходе на который system.js будет загружать соответствующий модуль.

Как можно увидеть, Root Application — простой HTML-файл с основными конфигурациями для загрузки других приложений. В одном микрофронтенд-приложении можно зарегистрировать множество микроприложений. Если при регистрации приложения в 3-м параметре метода registerApplication указать просто /, такое приложение будет загружаться для каждого доступного адреса. Именно такой подход желательно использовать для создания навигационной панели или частей приложения, которые являются общими и не должны загружаться повторно при переходе между приложениями.

Полностью рабочий пример того, как это работает, можно найти в моем GitHub-репозитории.

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

Нам, разработчикам, далеко не всегда приходится создавать приложения с нуля. Single-spa дает возможность полностью использовать существующие приложения как элементы микрофронтенд-архитектуры, будь то Root Application или асинхронно загружаемые микроприложения.

Если из существующего приложения нужно создать микроприложение, совместимое с single-spa и имеющее возможность загружаться асинхронно, в первую очередь нужно искать соответствующий враппер. Для большинства современных фреймворков и библиотек single-spa предоставляет врапперы, готовые к загрузке через npm, а также документацию, описывающую, как их правильно настроить. Если же для используемого фреймворка не существует готового враппера, то всегда можно написать его самостоятельно. Настроив враппер и собрав amd-модуль, вы получите готовое к подключению микроприложение; все, что останется, это добавить соответствующую конфигурацию в Root Application. С собственного опыта скажу, что для современных фреймворков и библиотек, таких как Angular 2+, React, Vue, которые собираются с помощью Webpack, конвертация в микроприложение проходит быстро и без дополнительных «танцев с бубнами».

Если из существующего приложения нужно создать Root Application, тогда в первую очередь нужно в index.html подключить библиотеки для single-spa и добавить все необходимые для загрузки других микроприложений конфигурации. После того как ваш index.html стал Root Application в single-spa, можно приступить к настройке остальной части приложения для корректной работы в single-spa-архитектуре так, как это описано в предыдущем абзаце.

Проблемы и недостатки

Большая проблема для обеих задач — это старые (legacy) приложения, написанные на старых фреймворках и собираемые с использованием старых инструментов. Например, приложение на AngularJS или Backbone, которое собирается с помощью gulp или grunt, может «сожрать» очень много вашего времени, прежде чем будет корректно сконфигурировано. С собственного опыта могу выделить следующие проблемные места:

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

На этапе конфигурации проблемы, с которыми вы столкнетесь, будут самые разнообразные, поэтому запаситесь терпением 😉

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

  • кеширование микроприложений. Без правильной стратегии по кешированию микроприложения, как и Root Application, будут кешироваться в браузере и игнорировать любые новые изменения, которые будут релизнуты;
  • дебаггинг. Если что-то не работает, дебаггинг без дополнительных конфигураций и надстроек может быть довольно тяжелым процессом, так как вам придется дебажить не приложения, а отдельные amd-модули без сорс мап;
  • общий размер приложения вместе со всеми асинхронными микроприложениями увеличится, в сравнении с монолитным подходом;
  • общая производительность микрофронтенд-приложения будет ниже, чем у монолита;
  • повторение кода. Повторная загрузка кода — как библиотек, так и целых фреймворков — ведет к ухудшению быстродействия всего приложения, а не только отдельной страницы. Single-spa дает возможность шарить зависимости между приложениями, но помните: чем больше у вас зависимостей, библиотек, компонент, которые шарятся между приложениями, тем больше ваша микрофронтенд-архитектура похожа на монолитную;
  • SEO-оптимизация. Загрузка отдельных приложений вместе с бутстрэппингом полностью проходит на клиенте, что значительно усложняет SEO-оптимизацию. Single-spa не поддерживает серверный рендеринг, для добавления поддержки можно использовать связку single-spa + Tailor.js.

Выводы

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

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

Один из частых вопросов: можно ли использовать single-spa для миграции с одного фреймворка на другой (например, с AngularJS на Angular 2+)? Можно, но не нужно: практически всегда есть более легкий и нативный способ миграции, который требует меньше конфигурационной работы и который будет работать быстрее.

источник