задний план
С появлением интернет-облака концепция концентрации множества различных сервисов на большой платформе и открытости для внешнего мира постепенно стала привычной или начала принимать форму этой «унифицированной платформы». В то же время фронтенд-сфера продолжала быстро развиваться: раннее MVC-решение jQuery+Backbone+Bootstrap долгое время поддерживало бизнес, позже начали появляться фреймворки MVVM, такие как Angular и Ember, с идеей Разделение фронтенда и бэкенда и компонентизация фронтенда В это время он достиг своего пика. В Китае фреймворк Vue лидирует с его простым и понятным API и отличной экологической поддержкой.Все больше и больше малых и средних предприятий и разработчиков начинают обращаться к лагерю Vue; в то же время, он уникален по дизайну React, чистый фреймворк View, начал расти, а его техническая идея Diff DOM привлекла большое количество разработчиков и стала самой горячей темой в крупных технических сообществах.
Вернемся к теме платформ. Большая платформа, которая объединяет различные предприятия. Во многих случаях бизнес делится на несколько подсистем для разработки, и, наконец, платформа обеспечивает унифицированный вход. В текущей быстро меняющейся внешней среде такие платформы должны учитывать следующие проблемы:
- Как интегрировать разные бизнес-подсистемы в большую платформу и открыть ее для внешнего мира?
- Как предоставить разрешения разным пользователям, чтобы они могли получить доступ к определенным бизнес-модулям платформы, и при этом запретить им доступ к неавторизованным бизнес-модулям?
- Как быстро получить доступ к новым подсистемам и выполнить управление версиями подсистем, чтобы обеспечить синхронизацию функций?
- Для старой системы, как стек технологий React из стека технологий Backbone или стек технологий Vue плавно обновить?
Далее я представлю нашу схему реализации, основанную на этих проблемах.
модель продукта
Для начала обсудим первый вопрос: как интегрировать разные бизнес-подсистемы в большую платформу и открыть ее внешнему миру?
Как показано на рисунке ниже, предположим, что у нас есть три бизнес-подсистемы.Если пользователь хочет использовать разные функции в трех системах, ему необходимо войти в три системы одновременно, а затем переключаться туда и обратно для работы.
На самом деле идеальное состояние состоит в том, что три подсистемы A, B и C находятся на одной большой платформе, а вход осуществляется через меню, и пользователь может свободно заходить на страницы любой подсистемы. Как показано ниже:
Обратите внимание, что на приведенном выше рисунке мы пометили приложение (приложение) для A, B и C, а большую платформу пометили как продукт.В дальнейшем для удобства описания мы будем называть каждую подсистему приложением, а платформу интеграция подсистем как продукта.
На самом деле, для реальных бизнес-сценариев, помимо улучшения пользовательского опыта, система, показанная на рисунке 2, имеет много преимуществ.По сравнению со вторым методом, если предприятие хочет продавать продукцию по бизнес-модулям, второй метод явно лучше. Затем разрешения модуля могут использовать новый модуль, а не предоставлять пользователю новую систему. Кроме того, для предприятий отказ от развертывания независимых бизнес-систем означает экономию доменных имен, серверов, ресурсов для эксплуатации и обслуживания, а также сокращение расходов предприятия.
Схема архитектуры
После определения модели Продукта, содержащего Приложение, мы затем рассмотрим форму, которая позволяет беспрепятственно переключать доступ к каждому Приложению в рамках Продукта.
Как показано на рисунке ниже, при доступе к странице мы добавляем путь доступа кприменить префикс, который определяет, к какому приложению в данный момент осуществляется доступ, и путь к странице, к которому в настоящее время осуществляется доступ после префикса пути к приложению, является обязательным условием.соглашение.
С точки зрения Продукта, мы надеемся, что когда пользователи будут использовать платформу, они не будут чувствовать, что каждое приложение переключает каждый системный модуль при переключении, поэтому Продукт должен контролировать время рендеринга представления всех приложений, то есть: для унифицированного управления всеми приложениями. Просмотр маршрутизации.
В то же время, чтобы отображать разные страницы просмотра для пользователей с разными разрешениями, мы также передаем данные о правах пользователя, возвращенные из бэкэнда, в Продукт, и Продукт автоматически отфильтрует маршруты без разрешений, как показано на следующем рисунке:
Здесь, поскольку переключение между приложениями должно быть похоже на переключение страниц системного приложения для пользователей, мы используем одностраничное приложение (SPA) для реализации управления маршрутизацией продукта.
Архитектура всей схемы показана на следующем рисунке:
В этой архитектурной схеме каждый суббизнес-модуль может быть динамически добавлен к большой платформе по мере необходимости, а префикс пути доступа может быть заблокирован, когда он не нужен; для платформенной системы каждый суббизнес-модуль подобен функциональному подключаемому модулю. , подключи и играй, нет необходимости То есть тянуть. Такая идея с подключаемыми модулями имеет долгую историю, мы называем ее «архитектура подключаемых приложений». По сравнению с традиционной интерфейсной архитектурой решение с подключаемой архитектурой приложений имеет следующие преимущества:
- Бизнес-модули разрабатываются распределенным образом, а складом кода легче управлять.
- Бизнес-модуль (приложение) отличается высокой переносимостью и может быть развернут отдельно или интегрирован в большую платформу (продукт).
- Код модуля очень связный и больше ориентирован на бизнес.
- В соответствии с принципом открытия-закрытия доступ к новым модулям не требует модификации существующих модулей и не влияет на функции других модулей.
Управление правами на ресурсы
Прежде чем представить конкретную реализацию архитектурного решения, нам нужно сначала провести некоторую подготовительную работу.Давайте сначала рассмотрим второй и третий вопросы, которые мы подняли в начале.
Сначала второй вопрос:Как предоставить разрешения разным пользователям, чтобы они могли получить доступ к определенным бизнес-модулям платформы, и при этом запретить им доступ к неавторизованным бизнес-модулям?
Выше кратко упоминалось, что серверная часть передает данные доступа в Продукт.Наш конкретный подход заключается в том, что каждое приложение передает свой полный путь маршрутизации в Продукт, и при запуске платформы (Продукта) Продукт будет логиниться из серверной части в соответствии с текущим логин. Пользователи получают свои авторизованные пути маршрутизации. При доступе к любому маршруту в приложении они будут сравниваться с авторизованными путями маршрутизации в первый раз. Пути маршрутизации, которые не удалось сравнить, автоматически приведут к несанкционированному просмотру страницы.
Что касается обслуживания разрешений на маршрутизацию, вы можете создать страницу управления для визуальной настройки маршрутизации, а также настроить степень уточнения разрешений в соответствии с вашими бизнес-условиями.
Тогда третий вопрос:Как быстро получить доступ к новым подсистемам и выполнить управление версиями подсистем, чтобы обеспечить синхронизацию функций?
Чтобы ответить на этот вопрос, нам нужно знать конкретный метод доступа каждого приложения. Как упоминалось выше, доступ к каждому приложению зависит от текущего префикса пути.Наш особый подход заключается в том, чтобы поддерживать адреса всех пакетов приложений на основе веб-пакета в серверной части и передавать отношение сопоставления конфигурации этих адресов пакетов в продукт. При первом доступе к приложению Product сначала загрузит пакет, связанный с приложением, а его js-пакет вызовет глобальный продукт для внедрения собственной информации о маршрутизации, а затем передаст последующую обработку маршрутизации продукту для выполнения.
Конечно, приведенная выше реализация будет связана с некоторыми проблемами при рендеринге представления App, которые мы представим в следующей схеме реализации.
План реализации
Мы обсудили много теоретического содержания выше, а затем переходим к ссылке на сухие товары: как реализовать подключаемую структуру приложения?
В соответствии с некоторыми идеями реализации, представленными выше, у нас сначала будет общая функциональная схема подключаемой инфраструктуры, которую необходимо реализовать:
- Самостоятельно внедрите маршрутизатор, который должен автоматически разрешать идентификатор приложения в соответствии с путем во время маршрутизации, а затем динамически загружать пакет ресурсов, соответствующий приложению, на основе идентификатора.
- Приложение выполняется сразу после загрузки пакета ресурсов js и автоматически вводит информацию о маршрутизации, связанную с приложением, в Product.
- После того, как приложение загрузило пакет ресурсов (скрипт будет выполнен сразу после загрузки), маршрутизатор пытается отобразить страницу просмотра приложения в соответствии с путем.
- После переключения маршрутов, если вы переключитесь на другие вложенные приложения, исходное приложение должно очистить связанную DOM и логику событий на основе собственного жизненного цикла.
Подводя итог, можно сказать, что наша подключаемая платформа приложений должна реализовывать следующие функциональные моменты: динамическую маршрутизацию, загрузку и планирование сценариев, отрисовку представления подприложения и управление жизненным циклом приложения.
Далее мы вводим идеи реализации каждой функциональной точки одну за другой.
динамическая маршрутизация
Говоря о маршрутизации, существуют разные реализации для разных технологических стеков. Например, у Vue есть vue-router, у React — react-router и т. д. Чтобы адаптироваться к ситуации, когда каждое вспомогательное приложение разрабатывается с использованием разных технических систем, нам необходимо стандартизировать и управлять конфигурацией маршрутизации. Поэтому нам нужен редизайн роутера, который должен уметь: динамически инжектить маршруты и поддерживать отрисовку компонентов разных технических систем одновременно.
Здесь мы используем более гибкийuniversal-router,Этоpathа такжеactionМетод конфигурации позволяет нам легко выполнять пользовательскую логику маршрутизации. Хотя он не поддерживает динамическую маршрутизацию инъекций, его код имеет разумную организацию со знаменитымhistoryбиблиотеку, я могу легко реализовать маршрутизатор, отвечающий моим потребностям.
Как показано ниже:
Загрузка скрипта и планирование
После выполнения основных функций динамической маршрутизации мы приступим к первому этапу обработки логики маршрутизации: динамической загрузке пакетов ресурсов, таких как сценарии, которые в настоящее время обращаются к приложению.
Во-первых, давайте проанализируем поток обработки: при запуске маршрутизации нам нужно использовать первое имя пути пути запроса (например,/a/b
Первый абзацa
), чтобы определить, какое приложение соответствует текущему маршрутизируемому пути. Если соответствующее приложение не внедрило информацию о маршрутизации, необходимо динамически загрузить пакет ресурсов приложения. После выполнения пакета ресурсов скрипта js последующая логика рендеринга продолжение.
Пакеты ресурсов приложения могут быть упакованы в различных формах, таких как AMD, Commonjs, UMD и т. д. Чтобы быть совместимыми с приложениями, которые можно развернуть отдельно и интегрировать в платформу, и поддерживать самые простые зависимости, мы по-прежнему используем форму пакета UMD на основе веб-пакета — пусть JS выполняется сразу после загрузки, что устраняет необходимость в зависимостях AMD. для загрузчиков пакетов, таких как Requirejs.
Затем, полагаясь на собственный механизм загрузки скриптов браузера, можно легко реализовать наш загрузчик пакетов ресурсов: используйте теги link и script соответственно для динамической вставки адреса пакета ресурсов под тегами head и body.
Конечно, некоторые люди будут рассматривать проблему загрузки зависимостей в порядке пакетов ресурсов. При нормальных обстоятельствах веб-пакет сам обрабатывает зависимости при упаковке.Если есть зависимости от нескольких подключаемых модулей пакета ресурсов, которые требуют последовательного выполнения (например, зависимости подключаемого модуля jQuery), во время загрузки может выполняться специальная последовательная обработка.
Процесс загрузки скрипта приложения показан на следующем рисунке:
Рендеринг представления приложения
После обработки динамической загрузки пакета ресурсов приложения мы реализуем основную функцию модуля маршрутизации: рендеринг представления приложения.
Прежде всего, представляя вышеприведенное решение, мы упомянули, что каждое вспомогательное приложение должно не только поддерживать отдельное развертывание, но также должно иметь доступ к продукту и работать на платформе. Поэтому мы должны понимать, что рендеринг каждого представления приложения должен выполняться каждым подприложением, а не фреймворком.
Если приведенный выше вывод кажется вам слишком резким, рассмотрите следующие два вопроса:
- Если рамочная структура равномерно оказывает результаты маршрутизации, как обеспечить совместимость с различными формами компонентов, таких как компонент Racture и Backbone?
- Если фреймворк единообразно отображает результаты маршрутизации, необходимо ввести интерфейс рендеринга, так как же обеспечить совместимость с версией интерфейса каждого дочернего приложения (например, версией ReactDOM и т. д.)?
Следовательно, чтобы отразить идею подключаемого дизайна фреймворка с учетом различных технических системных приложений, мы должны извлечь рендеринг представления приложения из фреймворка.
Итак, что еще нужно сделать маршрутизации фреймворка в логике рендеринга представления?
Вскоре мы подумаем о проблемах, которые возникают после извлечения логики рендеринга представления: каждое подприложение должно реализовывать рендеринг само по себе, так в чем же улучшается эффект фреймворка? Как должен быть унифицирован интерфейс рендеринга?
О принципе «открыто-закрыто» упоминалось выше, а основной идеей дизайна принципа «открыто-закрыто» является объектно-ориентированное проектирование. Наше решение:
- Предоставьте базовый класс приложения, интерфейс рендеринга спецификации, каждое подприложение должно внедрять экземпляры приложения, унаследованные от базового класса приложения, при внедрении приложений.
- Два класса приложений реализации рендеринга (оба унаследованы от базового класса Application) предоставляются по умолчанию: широко используемое приложение React и более применимое базовое приложение.
Во входном JS-файле каждого вложенного приложения вы можете напрямую создать экземпляр ReactApplication или BackboneApplication в соответствии с вашей собственной технической системой, или вы можете наследовать от базового класса Application для реализации интерфейса рендеринга. Конечно, если ваш собственный класс приложений используется чаще, вы можете внести его в виде подключаемого модуля.
Пример кода для базового класса Application:
// application/index.js
class Application {
static DEFAULTS = {
// ...
}
constructor(options = {}) {
this._options = Object.assign({}, DEFAULTS, options);
}
start() {
// 启动应用,开启 view 的路径变化监听事件
}
stop() {
// 停止路径变化监听事件
}
renderLayout() {
// 渲染布局的接口
}
render() {
// 渲染主体内容的接口
}
// ...
}
Пример кода реализации класса ReactApplication:
// application/react/index.js
import Application from '../index.js';
class ReactApplication extends Application {
render(err, children, params = {}) {
if (err) {
// 渲染错误页
throw err;
}
// React 和 ReactDOM 在实例化时由 App 自己传入,便于各 App 自己控制 React 版本
const { React, ReactDOM } = this._options;
ReactDOM.render(children, this._container);
}
}
Пример кода реализации класса BackboneApplication:
// application/backbone/index.js
import Application from '../index.js';
class BackboneApplication extends Application {
render(err, viewAction, params = {}) {
if (err) {
// 渲染错误页
throw err;
}
if (viewAction.prototype && isFunction(viewAction.prototype.render)) {
this._currentView = new viewAction(params);
return this._currentView.render();
}
if (typeof viewAction.render === 'function') {
return viewAction.render(params);
}
}
}
После того, как логика рендеринга будет передана каждому подприложению для самостоятельной реализации, мы сможем избежать реализации другой логики рендеринга в соответствии с разными техническими системами в классе View фреймворка. Если подприложение переходит на другие методы рендеринга, отличные от Backbone и React, нам не нужно изменять реализацию фреймворка и повторно выпускать новую версию.
另外,除了应用实例外,我们还需要构造一个 Product 类,提供注入应用实例的入口。 Пример кода выглядит следующим образом:
class Product {
static registerApplication = (app) => {
// 缓存 app 实例,并注入 app 路由
}
}
Во входном JS-файле каждого вложенного приложения вызовите класс Product, чтобы внедрить текущий экземпляр приложения (в качестве примера возьмем приложение React):
// src/app.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Product, ReactApplication } from 'plugin-pkg';
const app = new ReactApplication({
React,
ReactDOM,
// ...
});
Product.registerApplication(app);
Управление жизненным циклом приложений
На данный момент, от динамической маршрутизации к рендерингу представления, у нас уже есть конкретные идеи реализации.Теперь рассмотрим проблему в практическом приложении: при переключении каждого подприложения будет заменяться DOM предыдущего приложения, но связанные события не будут очищается должным образом. Взяв в качестве примера React, мы напрямую заменили DOM-контент, но событие UnMount компонента React не сработало правильно, то же самое верно и для обратного вызова уничтожения Backbone View.
Итак, нам нужно добавить интерфейс уничтожения в класс Application:
class Application {
destroy() {
// 在当前 App 实例切换出去时调用
}
}
Помимо события уничтожения, иногда требуется некоторая унифицированная обработка после включения приложения. Также нужно добавить готовый интерфейс:
class Application {
ready() {
// 在当前 App 实例切换进来时调用
}
}
Для обработки и реализации жизненного цикла каждый экземпляр приложения может реализовать соответствующую логику в соответствии со своей реальной ситуацией.
При переключении приложений платформа должна автоматически вызывать интерфейс уничтожения предыдущего экземпляра приложения, а затем автоматически вызывать интерфейс подготовки текущего приложения после рендеринга приложения.
конфигурация сборки
Вышеупомянутое содержимое — это все функции, которые должна реализовать подключаемая платформа. Кроме того, каждое вложенное приложение должно быть настроено единообразно при упаковке. Например, зависимости фреймворка должны быть установлены в виде внешних, а пакет ресурсов не будет введен при упаковке. Поскольку каждый из наших пакетов ресурсов App JS представляет собой прямое выполнение пакетов UMD, глобальные переменные пакета фреймворка, введенные продуктом, могут использоваться в реальной среде выполнения.
Пример кода для настройки веб-пакета выглядит следующим образом:
// webpack.config.js
const path = require('path');
const resolveApp = relativePath => path.join(process.cwd(), relativePath);
module.exports = {
entry: {
bundle: resolveApp('src/app.js');
},
module: {
// ...
},
plugins: [
// ...
],
externals: {
'plugin-pkg': 'Plugin',
},
};
Таким образом, он не только совместим с двумя формами независимого развертывания и интеграции в платформу, но также может унифицированным образом использовать подключаемый фреймворк платформы в режиме подключаемой платформы, что удобно для унифицированного апгрейд платформы.
Суммировать
Приведенный выше дизайн подключаемого приложения обусловлен тем, что рассматриваются подбизнес-модули, совместимые с различными техническими системами, реализация маршрутизации немного усложняется, а динамическая загрузка скриптов относительно проста. В реальных бизнес-требованиях, если определена единая техническая система, в большинстве случаев нет необходимости рассматривать вопрос совместимости с разными подбизнес-модулями, вполне можно выбрать техническую систему (типа Vue или React ) для достижения этого.Вероятно, только разрешения для обработки этого маленького кусочка.
Таким образом, приведенный выше контент предназначен только для справки.В соответствии с реальным бизнесом это лучшее решение для разработки подключаемого модуля, подходящего для вашего собственного бизнеса.
Ссылаться на
Статья может быть воспроизведена по желанию, но просьба сохранитьОригинальная ссылка. Добро пожаловать!ES2049 Studio, отправьте свое резюме на caijun.hcj(at)alibaba-inc.com.