Начало работы с микро-фронтендами

JavaScript

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

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

Перевод начинается с сокращений.Оригинальная ссылка


введение

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

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


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

Предположим, вы хотите создать прогрессивное веб-приложение, но испытываете трудности с реализацией новых функций в существующем монолите. Допустим, вы хотите начать использовать новый синтаксис JS (или TypeScript), но не можете использовать соответствующие инструменты сборки в существующем процессе сборки. Или, может быть, вы просто хотите расширить свою команду разработчиков, чтобы несколько команд могли работать над продуктом одновременно, но связанность и сложность существующего приложения ставит каждого разработчика в зависимость друг от друга. Это настоящие проблемы, которые сильно снижают продуктивность больших команд.

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

Архитектурный стиль, объединяющий отдельные клиентские приложения в единое целое.

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

Преимущества микро-фронтендов

Инкрементное обновление

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

Простая несвязанная кодовая база

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

Автономное развертывание

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

Командная автономия

Суммировать

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

пример

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

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

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

интегрированный

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

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

Интеграция бэкэнд-шаблонов

Мы начали очень традиционным способом, преобразовывая несколько шаблонов в HTML на сервере. У нас есть index.html, который включает в себя все общие элементы страницы, а затем используйте include для добавления других шаблонов:

<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <title>Feed me</title>
  </head>
  <body>
    <h1>🍽 Feed me</h1>
    <!--# include file="$PAGE.html" -->
  </body>
</html>

Затем настройте nginx

server {
    listen 8080;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;
    ssi on;

    # 将 / 重定向到 /browse
    rewrite ^/$ http://localhost:8080/browse redirect;

    # 根据路径访问 html 
    location /browse {
      set $PAGE 'browse';
    }
    location /order {
      set $PAGE 'order';
    }
    location /profile {
      set $PAGE 'profile'
    }

    # 所有其他路径都渲染 /index.html
    error_page 404 /index.html;
}

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

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

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

интеграция пакетов

Один из подходов, который кто-то может использовать, состоит в том, чтобы опубликовать каждый микрофронтенд как пакет узла, а приложение-контейнер иметь все микрофронтенды в качестве зависимостей. Например, этот package.json:

{
  "name": "@feed-me/container",
  "version": "1.0.0",
  "description": "A food delivery web app",
  "dependencies": {
    "@feed-me/browse-restaurants": "^1.2.3",
    "@feed-me/order-food": "^4.5.6",
    "@feed-me/user-profile": "^7.8.9"
  }
}

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

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

Интеграция через iframe

iframes — один из самых простых способов интеграции. По сути, страницы внутри iframe полностью автономны и могут быть легко созданы. И фреймы также предоставляют множество механизмов изоляции.

<html>
  <head>
    <title>Feed me!</title>
  </head>
  <body>
    <h1>Welcome to Feed me!</h1>

    <iframe id="micro-frontend-container"></iframe>

    <script type="text/javascript">
      const microFrontendsByRoute = {
        '/': 'https://browse.example.com/index.html',
        '/order-food': 'https://order.example.com/index.html',
        '/user-profile': 'https://profile.example.com/index.html',
      };

      const iframe = document.getElementById('micro-frontend-container');
      iframe.src = microFrontendsByRoute[window.location.pathname];
    </script>
  </body>
</html>

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

Однако, если мы вернемся к основным преимуществам микрофронтендов, перечисленным ранее, iframes хорошо подходят, если мы тщательно относимся к тому, как мы разделяем наши микро-приложения и формируем наши команды.

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

Интеграция с JS

Этот метод, вероятно, является наиболее гибким и наиболее часто используемым методом. По одному на каждый микро-интерфейс<script>теги и экспортирует глобальную переменную при загрузке. Затем приложение-контейнер определяет, какие микроприложения следует установить, и вызывает соответствующие функции, чтобы сообщить микроприложениям, когда и где выполнять рендеринг.

<html>
  <head>
    <title>Feed me!</title>
  </head>
  <body>
    <h1>Welcome to Feed me!</h1>

    <!-- 这些脚本不会马上渲染应用 -->
    <!-- 而是分别暴露全局变量 -->
    <script src="https://browse.example.com/bundle.js"></script>
    <script src="https://order.example.com/bundle.js"></script>
    <script src="https://profile.example.com/bundle.js"></script>

    <div id="micro-frontend-root"></div>

    <script type="text/javascript">
      // 这些全局函数是上面脚本暴露的
      const microFrontendsByRoute = {
        '/': window.renderBrowseRestaurants,
        '/order-food': window.renderOrderFood,
        '/user-profile': window.renderUserProfile,
      };
      const renderFunction = microFrontendsByRoute[window.location.pathname];

      // 渲染第一个微应用
      renderFunction('micro-frontend-root');
    </script>
  </body>
</html>

Выше приведен очень простой пример, демонстрирующий общую идею интеграции JS.

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

В отличие от интеграции с iframe, у нас есть полная гибкость, вы можете использовать JS для контроля загрузки каждого приложения и дополнительных параметров при рендеринге приложения.

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

Интеграция через веб-компонент

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

<html>
  <head>
    <title>Feed me!</title>
  </head>
  <body>
    <h1>Welcome to Feed me!</h1>

     <!-- 这些脚本不会马上渲染应用 -->
    <!-- 而是分别提供自定义标签 -->
    <script src="https://browse.example.com/bundle.js"></script>
    <script src="https://order.example.com/bundle.js"></script>
    <script src="https://profile.example.com/bundle.js"></script>

    <div id="micro-frontend-root"></div>

    <script type="text/javascript">
      // 这些标签名是上面代码定义的
      const webComponentsByRoute = {
        '/': 'micro-frontend-browse-restaurants',
        '/order-food': 'micro-frontend-order-food',
        '/user-profile': 'micro-frontend-user-profile',
      };
      const webComponentType = webComponentsByRoute[window.location.pathname];

      // 渲染第一个微应用(自定义标签)
      const root = document.getElementById('micro-frontend-root');
      const webComponent = document.createElement(webComponentType);
      root.appendChild(webComponent);
    </script>
  </body>
</html>

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

стиль

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

Например, если таблица стилей для микроинтерфейса одной команды — h2 {color: black; }, а для другой команды — h2 {color: blue; }, и оба селектора прикреплены к одной и той же странице, возникнет конфликт!

Это не новая проблема, но поскольку эти селекторы были написаны разными командами в разное время, а код может быть разбросан по разным библиотекам, избежать этого сложнее.

За прошедшие годы появилось много способов сделать CSS более управляемым. Некоторые люди предпочитают использовать строгие соглашения об именах, такие как BEM, чтобы гарантировать, что область действия селектора достаточно мала. Другие используют препроцессоры, такие как SASS, вложенность селекторов которых можно использовать в качестве пространств имен. Более новый подход заключается в программном написании CSS с помощью модулей CSS или различных библиотек CSS-in-JS. Некоторые разработчики также используют теневой DOM для изоляции стилей.

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

Совместное использование библиотеки компонентов

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

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

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

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

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

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

Люди, которые поддерживают общие библиотеки, должны быть очень техническими и иметь плохие коммуникативные навыки.

Общайтесь через микроприложения

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

Однако потребность в обмене данными между приложениями по-прежнему существует.

  1. Использование пользовательских сообщений о событиях — хороший способ уменьшить связанность. Однако это размывает интерфейс между микроприложениями.
  2. Рассмотрим механизм, распространенный в приложениях React: передача обратных вызовов и данных сверху вниз.
  3. Третий вариант — использовать адресную строку в качестве коммуникационного моста, который мы подробно рассмотрим позже.

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

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

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

Внутренняя связь

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

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

Шаблон, который мы рекомендуем, — это шаблон Backends For Frontends, где каждое клиентское приложение имеет соответствующий серверный модуль, а цель серверного модуля — просто удовлетворить потребности этого внешнего интерфейса. Первоначальная степень детализации модели BFF может заключаться в том, что каждая внешняя платформа (страница ПК, мобильная страница и т. д.) соответствует внутреннему приложению, но в конечном итоге она станет внутренним приложением для каждого микроприложения.

Здесь следует отметить, что приложение Backeng может иметь независимую бизнес-логику и базу данных, или он может быть только агрегатором в нисходящих службах. Если приложение Micro-Frontend имеет только одну API для связи, и что API довольно стабильна, наращивая отдельную бэкэнду, потому что она не может быть никакой ценности вообще. Руководящий принцип состоит в том, что сборное построение микрофронтной приложения не нужно ждать других команд, чтобы построить что-то для него.

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

Еще один распространенный вопрос: как сделать аутентификацию и аутентификацию?

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

контрольная работа

С точки зрения тестирования мы не видим большой разницы между монолитными интерфейсами и микроинтерфейсами.

Очевидный пробел — это интеграционное тестирование различных микрофронтендов для контейнерных приложений.

подробный пример

Далее давайте реализуем подробный пример.

В основном рассказывается, как приложения-контейнеры и микроприложения могут быть интегрированы вместе с помощью JavaScript, так как это, вероятно, самая интересная и сложная часть.

ты сможешьdemo.microfrontends.comОзнакомьтесь с окончательными результатами развертывания, полный исходный код можно найти по адресуGithubсм. выше.

Проект реализован с использованием React.js, и стоит отметить, что React — не единственный вариант.

контейнер

мы начнемконтейнерНачните, потому что это наша точка входа. пакет.json:

{
  "name": "@micro-frontends-demo/container",
  "description": "Entry point and container for a micro frontends demo",
  "scripts": {
    "start": "PORT=3000 react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test"
  },
  "dependencies": {
    "react": "^16.4.0",
    "react-dom": "^16.4.0",
    "react-router-dom": "^4.2.2",
    "react-scripts": "^2.1.8"
  },
  "devDependencies": {
    "enzyme": "^3.3.0",
    "enzyme-adapter-react-16": "^1.1.1",
    "jest-enzyme": "^6.0.2",
    "react-app-rewire-micro-frontends": "^0.0.1",
    "react-app-rewired": "^2.1.1"
  },
  "config-overrides-path": "node_modules/react-app-rewire-micro-frontends"
}

Как видите, это приложение React, созданное с помощью create-react-app.

Обратите внимание, что я не включил другие микроприложения в зависимости от package.json.

Если вы хотите узнать, как выбирать и отображать микроприложения, взгляните на App.js. Мы используем React Router для сопоставления текущего URL со списком предопределенных маршрутов и рендеринга соответствующего компонента:

<Switch>
  <Route exact path="/" component={Browse} />
  <Route exact path="/restaurant/:id" component={Restaurant} />
  <Route exact path="/random" render={Random} />
</Switch>

Компоненты «Браузер» и «Ресторан» выглядят следующим образом:

const Browse = ({ history }) => (
  <MicroFrontend history={history} name="Browse" host={browseHost} />
);
const Restaurant = ({ history }) => (
  <MicroFrontend history={history} name="Restaurant" host={restaurantHost} />
);

Оба компонента отображают компонент MicroFrontend. В дополнение к объекту истории (который станет важным позже), мы также указываем уникальное имя для приложения и соответствующий серверный хост. Значение host может бытьhttp://localhost:3001илиbrowse.demo.microfrontends.com.

MicroFrontend — это просто еще один компонент React:

class MicroFrontend extends React.Component {
  render() {
    return <main id={`${this.props.name}-container`} />;
  }
}

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

  // class MicroFrontend
  componentDidMount() {
    const { name, host } = this.props;
    const scriptId = `micro-frontend-script-${name}`;

    if (document.getElementById(scriptId)) {
      this.renderMicroFrontend();
      return;
    }

    fetch(`${host}/asset-manifest.json`)
      .then(res => res.json())
      .then(manifest => {
        const script = document.createElement('script');
        script.id = scriptId;
        script.src = `${host}${manifest['main.js']}`;
        script.onload = this.renderMicroFrontend;
        document.head.appendChild(script);
      });
  }

URL-адрес скрипта должен быть получен из файла манифеста, потому что react-scripts выводит скомпилированные файлы JavaScript с хэшами в именах файлов для целей кэширования.

После установки URL скрипта остальное добавляется в документ и для инициализации:

  // class MicroFrontend
  renderMicroFrontend = () => {
    const { name, history } = this.props;

    window[`render${name}`](`${name}-container`, history);
    // E.g.: window.renderBrowse('browse-container', history);
  };

Последнее, что нужно сделать, это очистить. Когда MicroFrontend удаляет компонент со страницы, мы также должны удалить связанное с ним микроприложение.

  componentWillUnmount() {
    const { name } = this.props;

    window[`unmount${name}`](`${name}-container`);
  }

Микро-интерфейсное приложение

Далее мы представим, как реализуется метод window.renderBrowse:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

window.renderBrowse = (containerId, history) => {
  ReactDOM.render(<App history={history} />, document.getElementById(containerId));
  registerServiceWorker();
};

window.unmountBrowse = containerId => {
  ReactDOM.unmountComponentAtNode(document.getElementById(containerId));
};

В приведенном выше коде используются ReactDOM.render и ReactDOM.unmountComponentAtNode.

Самостоятельно разрабатывать и запускать микрофронтенды. Каждое приложение микрофронтенда также имеет дополнительный index.html для независимого рендеринга вне контейнера:

<html lang="en">
  <head>
    <title>Restaurant order</title>
  </head>
  <body>
    <main id="container"></main>
    <script type="text/javascript">
      window.onload = () => {
        window.renderRestaurant('container');
      };
    </script>
  </body>
</html>

Отныне микрофронтенды в основном представляют собой простые приложения React. 'browser' Список ресторанов App, предоставляющий<input>для поиска и фильтрации и использования<Link>Подводя итоги, пользователь переходит в определенный ресторан при нажатии. Затем мы переключимся на второй 'order' Микроприложение, отображающее страницу ресторана с меню.

Связь между приложениями через маршрутизацию

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

Задействовано три приложения React, все из которых используют React Router для маршрутизации, но инициализируются двумя немного разными способами.

Для приложений-контейнеров мы создаем один, который внутри создает экземпляр объекта истории, который мы используем для обработки истории на стороне клиента, а также можно использовать для объединения нескольких маршрутизаторов React. Способ инициализации маршрута

<Router history={this.props.history}>

Эта история предоставляется контейнерным приложением, и все микро-приложения имеют эту историю объекта. Это делает очень простым в использовании URL-адреса как способ передачи сообщений. Например, у нас есть такая ссылка:

<Link to={`/restaurant/${restaurant.id}`}>

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

Я надеюсь, что этот пример демонстрирует гибкость и мощь URL-адресов. Использование URL-адресов для обмена сообщениями должно удовлетворять следующим условиям:

  • Структура URL открыта и прозрачна.
  • Доступ к URL является глобальным
  • Длина URL ограничена
  • Моделирование пользователя, URL-адрес должен быть простым для понимания
  • Заявлено, а не заказано. То есть URL-адрес представляет собой местоположение текущей страницы, а не то, что является текущей страницей?
  • Это заставляет приложения микро-интерфейса взаимодействовать косвенно, вместо того, чтобы полагаться напрямую на

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

делиться контентом

Хотя мы хотим, чтобы каждая команда и микроприложение были максимально независимыми, некоторые вещи являются общими.

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

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

Первым шагом является выбор зависимостей для совместного использования. Анализ нашего скомпилированного кода показывает, что около 50% кода внесено в систему react и react-dom. Эти две библиотеки являются нашими основными зависимостями, поэтому было бы очень эффективно извлекать эти две библиотеки по отдельности как общие библиотеки. Наконец, это очень стабильные и зрелые библиотеки, а обновления выполняются осторожно, поэтому обновление не должно быть слишком сложным.

Что касается того, как извлечь, все, что нам нужно сделать, это пометить библиотеку как внешнюю в конфигурации веб-пакета:

module.exports = (config, env) => {
  config.externals = {
    react: 'React',
    'react-dom': 'ReactDOM'
  }
  return config;
};

Затем добавьте несколько тегов script в каждый файл index.html, чтобы получить две библиотеки с сервера общего содержимого:

<body>
  <div id="root"></div>
  <script src="%REACT_APP_CONTENT_HOST%/react.prod-16.8.6.min.js"></script>
  <script src="%REACT_APP_CONTENT_HOST%/react-dom.prod-16.8.6.min.js"></script>
</body>

недостаток

Как и во всех архитектурах, в архитектурах микрофронтенда есть некоторые компромиссы. Хотя мы получаем преимущества, есть и издержки.

Загрузки

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

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

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

экологические различия

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

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

Сложность управления

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

  • Достаточно ли средств автоматизации для настройки и управления необходимой дополнительной инфраструктурой?
  • Можно ли масштабировать процесс разработки, тестирования и выпуска интерфейса для нескольких приложений?
  • Готовы ли вы сделать процесс принятия решений более расплывчатым и даже неконтролируемым?
  • Как вы будете обеспечивать качество, согласованность и/или уровни управления несколькими независимыми интерфейсными кодовыми базами?

в заключении

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

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


Перевод завершен.

Более подробное чтение: