Что такое микросервисы? Первый взглядВикипедияОпределение:
Микросервисы (английский язык: Microservices) — это стиль архитектуры программного обеспечения, основанный на небольших строительных блоках (Small Building Blocks), сосредоточенных на одной ответственности и функции, и использующий модульный способ составления сложных крупномасштабных приложений.Функциональные блоки взаимодействуют друг с другом. другой, использующий набор API, не зависящий от языка/языка.
Другими словами, это декомпозиция большого сложного приложения на несколько сервисов, каждый из которых является как бы компонентом, который объединяется для построения всего приложения.
Представьте, каково было бы поддерживать приложение с сотнями функций и сотнями тысяч строк кода?
- Один фрагмент кода может повлиять на работу всего тела, изменение одного фрагмента кода требует повторного развертывания всего приложения. Часто возникает ситуация «модифицируй одну минуту, компилируй полчаса».
- Модули кода сложны и взаимозависимы. Изменение кода в одном месте часто влияет на другие функции приложения.
Каковы преимущества рефакторинга всего приложения с использованием микросервисов?
Приложение разбивается на несколько сервисов, каждый из которых выполняет свои внутренние функции. Например, исходное приложение имеет четыре страницы abcd, а теперь оно разделено на две службы. Первая служба имеет две страницы ab, а вторая служба — две страницы cd. Комбинация такая же, как и в исходном приложении.
Когда одна из служб приложения выходит из строя, другие службы по-прежнему могут быть доступны в обычном режиме. Например, если первая служба выйдет из строя, страница ab будет недоступна, но страница cd останется доступной.
выгода: различные сервисы работают независимо, разделяя сервисы. Мы можем думать о службах как о компонентах, как описано в главе 3 «Внешняя компонентизация» этой небольшой книги. Каждой службой можно управлять независимо. Изменение службы не влияет на работу всего приложения, а влияет только на функции, предоставляемые службой.
Кроме того, вы можете быстро добавлять и удалять функции во время разработки. Например, на сайте электронной коммерции страницы событий, запущенные в разные праздники, могут быть удалены сразу после события.
трудность: Определить границы сервиса непросто. Когда приложение имеет слишком много функций, связь между несколькими функциональными точками часто оказывается более глубокой. Таким образом, трудно определить, какой службе должна принадлежать эта функция.
PS: Micro front-end — это применение микросервисов во фронтенде, то есть front-end микросервисы.
Практика микросервисов
Теперь мы будем использовать фреймворк микро-фронтенда.qiankunдля создания микро-интерфейса приложения. Фреймворк qiankun выбран потому, что он имеет следующие преимущества:
- Независимо от стека технологий можно получить доступ к приложениям любого стека технологий.
- Изоляция стилей, стили между подприложениями не мешают друг другу.
- Области действия JavaScript вложенных приложений изолированы друг от друга.
- Предварительная загрузка ресурсов, которая предварительно загружает неоткрытые ресурсы микро-приложений во время простоя браузера, чтобы ускорить открытие микро-приложений.
изоляция стиля
Принципы разделения стилейДа: каждый раз, когда вы переключаете субприложение, будет загружаться файл css, соответствующий этому субприложению. В то же время исходный файл стиля подприложения будет удален, чтобы добиться эффекта изоляции стиля.
Мы можем смоделировать этот эффект сами:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="index.css">
<body>
<div>移除样式文件后将不会变色</div>
</body>
</html>
/* index.css */
body {
color: red;
}
Теперь давайте добавим кусок кода JavaScript для удаления файла стиля после загрузки файла стиля:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="index.css">
<body>
<div>移除样式文件后将不会变色</div>
<script>
setTimeout(() => {
const link = document.querySelector('link')
link.parentNode.removeChild(link)
}, 3000)
</script>
</body>
</html>
Затем откройте страницу и посмотрите, вы можете обнаружить, что стиль шрифта исчез через 3 секунды.
Изоляция области действия JavaScript
Основное приложение записывает текущее глобальное состояние перед переключением подприложений, а затем восстанавливает глобальное состояние после отключения подприложения. Предположим, что текущее глобальное состояние выглядит так:
const global = { a: 1 }
После входа в подприложение, независимо от того, как изменится глобальное состояние, исходное глобальное состояние будет восстановлено при переключении подприложения в будущем:
// global
{ a: 1 }
Официальный также предоставляет изображение, чтобы помочь нам понять этот механизм:
Хорошо, теперь давайте создадим приложение с микроинтерфейсом. Это микрофронтенд-приложение состоит из трех частей:
- main: основное приложение, созданное с помощью vue-cli.
- vue: подприложение, созданное с помощью vue-cli.
- реагировать: под-приложение, использующее версию реакции 16.
Соответствующие каталоги следующие:
-main
-vue
-react
Создайте основное приложение
Мы используем vue-cli для создания основного приложения (а затем выполняемnpm i qiankunУстановите фреймворк qiankun):
vue create main
Если основное приложение действует только как базовое, то есть используется только для переключения подприложений. Тогда вам не нужно устанавливать vue-router и vuex.
МодернизацияApp.vueдокумент
Основное приложение должно предоставлять элемент, который может устанавливать вспомогательные приложения, поэтому нам нужноApp.vueИзмените файл:
<template>
<div class="mainapp">
<!-- 标题栏 -->
<header class="mainapp-header">
<h1>QianKun</h1>
</header>
<div class="mainapp-main">
<!-- 侧边栏 -->
<ul class="mainapp-sidemenu">
<li @click="push('/vue')">Vue</li>
<li @click="push('/react')">React</li>
</ul>
<!-- 子应用 -->
<main class="subapp-container">
<h4 v-if="loading" class="subapp-loading">Loading...</h4>
<div id="subapp-viewport"></div>
</main>
</div>
</div>
</template>
<script>
export default {
name: 'App',
props: {
loading: Boolean,
},
methods: {
push(subapp) { history.pushState(null, subapp, subapp) }
}
}
</script>
Вы можете видеть, что элемент, который мы используем для установки подприложения,#subapp-viewport, кроме функции переключения подприложений:
<!-- 侧边栏 -->
<ul class="mainapp-sidemenu">
<li @click="push('/vue')">Vue</li>
<li @click="push('/react')">React</li>
</ul>
Модернизацияmain.js
Согласно документации qiankun, вам нужно использоватьregisterMicroApps()а такжеstart()Способы регистрации подприложений и запуска основного приложения:
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'react app', // app name registered
entry: '//localhost:7100',
container: '#yourContainer',
activeRule: '/yourActiveRule',
},
{
name: 'vue app',
entry: { scripts: ['//localhost:7100/main.js'] },
container: '#yourContainer2',
activeRule: '/yourActiveRule2',
},
]);
start();
Итак, теперь необходимоmain.jsИзмените файл:
import Vue from 'vue'
import App from './App'
import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start, initGlobalState } from 'qiankun'
let app = null
function render({ loading }) {
if (!app) {
app = new Vue({
el: '#app',
data() {
return {
loading,
}
},
render(h) {
return h(App, {
props: {
loading: this.loading
}
})
}
});
} else {
app.loading = loading
}
}
/**
* Step1 初始化应用(可选)
*/
render({ loading: true })
const loader = (loading) => render({ loading })
/**
* Step2 注册子应用
*/
registerMicroApps(
[
{
name: 'vue', // 子应用名称
entry: '//localhost:8001', // 子应用入口地址
container: '#subapp-viewport',
loader,
activeRule: '/vue', // 子应用触发路由
},
{
name: 'react',
entry: '//localhost:8002',
container: '#subapp-viewport',
loader,
activeRule: '/react',
},
],
// 子应用生命周期事件
{
beforeLoad: [
app => {
console.log('[LifeCycle] before load %c%s', 'color: green', app.name)
},
],
beforeMount: [
app => {
console.log('[LifeCycle] before mount %c%s', 'color: green', app.name)
},
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green', app.name)
},
],
},
)
// 定义全局状态,可以在主应用、子应用中使用
const { onGlobalStateChange, setGlobalState } = initGlobalState({
user: 'qiankun',
})
// 监听全局状态变化
onGlobalStateChange((value, prev) => console.log('[onGlobalStateChange - master]:', value, prev))
// 设置全局状态
setGlobalState({
ignore: 'master',
user: {
name: 'master',
},
})
/**
* Step3 设置默认进入的子应用
*/
setDefaultMountApp('/vue')
/**
* Step4 启动应用
*/
start()
runAfterFirstMounted(() => {
console.log('[MainApp] first app mounted')
})
Вот несколько вещей, о которых следует помнить:
- название вспомогательного приложения
nameобязательно и под приложениемpackage.jsonв файлеnameТакой же. - Каждое подприложение имеет один
loader()метод, который предназначен для решения ситуации, когда пользователь входит на страницу непосредственно из маршрута подприложения. При входе на подстраницу оцените, загружено ли основное приложение, если нет, загрузите его, и пропустите, если есть. - Чтобы предотвратить отображение пустых страниц при переключении подприложений, вы должны предоставить
loadingконфигурация. - При настройке адреса входа субприложения укажите непосредственно адрес доступа субприложения.
Изменить порт доступа
Порт доступа по умолчанию для vue-cli, как правило, 8080. Для того, чтобы он соответствовал подприложению, необходимо изменить порт основного приложения на 8000 (подприложения — 8001 и 8002 соответственно). Создайтеvue.config.jsфайл, измените порт доступа на 8000:
module.exports = {
devServer: {
port: 8000,
}
}
На данный момент основное приложение было преобразовано.
Создание дополнительных приложений
Подприложениям не нужно вводить зависимости qiankun, им нужно только предоставить несколько функций жизненного цикла:
-
bootstrap, срабатывает при первом запуске дочернего приложения. -
mount, который срабатывает каждый раз при запуске дочернего приложения. -
unmount, срабатывает при переключении/удалении вспомогательного приложения.
дочернее приложениеmain.jsИзмените файл:
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from './App.vue'
import routes from './router'
import store from './store'
Vue.config.productionTip = false
let router = null
let instance = null
function render(props = {}) {
const { container } = props
router = new VueRouter({
// hash 模式不需要下面两行
base: window.__POWERED_BY_QIANKUN__ ? '/vue' : '/',
mode: 'history',
routes,
})
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app')
}
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
} else {
render()
}
function storeTest(props) {
props.onGlobalStateChange &&
props.onGlobalStateChange(
(value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
true,
)
props.setGlobalState &&
props.setGlobalState({
ignore: props.name,
user: {
name: props.name,
},
})
}
export async function bootstrap() {
console.log('[vue] vue app bootstraped')
}
export async function mount(props) {
console.log('[vue] props from main framework', props)
storeTest(props)
render(props)
}
export async function unmount() {
instance.$destroy()
instance.$el.innerHTML = ''
instance = null
router = null
}
Вы можете видеть, что он выставлен в конце файлаbootstrap mount unmountТри функции жизненного цикла. Кроме того, при монтировании подприложений нужно обращать внимание на то, запускается ли подприложение под основным приложением или запускается независимо:container ? container.querySelector('#app') : '#app'.
Настройка элементов упаковки
Согласно документации qiankun, вам необходимо внести следующие изменения в элементы конфигурации упаковки субприложения:
const packageName = require('./package.json').name;
module.exports = {
output: {
library: `${packageName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${packageName}`,
},
};
Итак, теперь нам также нужно создать в каталоге суб-приложенияvue.config.jsфайла введите следующий код:
// vue.config.js
const { name } = require('./package.json')
module.exports = {
configureWebpack: {
output: {
// 把子应用打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`
}
},
devServer: {
port: 8001,
headers: {
'Access-Control-Allow-Origin': '*'
}
}
}
vue.config.jsВ файле есть несколько предостережений:
- Основное приложение и вспомогательные приложения работают на разных портах, поэтому необходимо установить междоменные заголовки.
'Access-Control-Allow-Origin': '*'. - Поскольку подприложение vue настроено в основном приложении и должно работать на порту 8001, его также необходимо
devServerИзмените порт здесь.
Метод трансформации другого реактивного подприложения такой же, как и у vue, поэтому я не буду повторяться здесь.
развертывать
Для развертывания проекта мы будем использовать экспресс, за исключением того, что нам нужно установить кросс-домен в подприложении, здесь особо не на что обращать внимание.
основной файл сервера приложенийmain-server.js:
const fs = require('fs')
const express = require('express')
const app = express()
const port = 8000
app.use(express.static('main-static'))
app.get('*', (req, res) => {
fs.readFile('./main-static/index.html', 'utf-8', (err, html) => {
res.send(html)
})
})
app.listen(port, () => {
console.log(`main app listening at http://localhost:${port}`)
})
файл дочернего сервера приложений vuevue-server.js:
const fs = require('fs')
const express = require('express')
const app = express()
const cors = require('cors')
const port = 8001
// 设置跨域
app.use(cors())
app.use(express.static('vue-static'))
app.get('*', (req, res) => {
fs.readFile('./vue-static/index.html', 'utf-8', (err, html) => {
res.send(html)
})
})
app.listen(port, () => {
console.log(`vue app listening at http://localhost:${port}`)
})
реагировать на файл сервера вспомогательного приложенияreact-server.js:
const fs = require('fs')
const express = require('express')
const app = express()
const cors = require('cors')
const port = 8002
// 设置跨域
app.use(cors())
app.use(express.static('react-static'))
app.get('*', (req, res) => {
fs.readFile('./react-static/index.html', 'utf-8', (err, html) => {
res.send(html)
})
})
app.listen(port, () => {
console.log(`react app listening at http://localhost:${port}`)
})
Кроме того, упакованные файлы этих трех приложений необходимо поместить вmain-static,vue-static,react-staticПод содержанием. Затем выполните команду отдельноnode main-server.js,node vue-server.js,node react-server.jsВы можете просмотреть развернутую страницу. Теперь каталог проекта выглядит следующим образом:
-main
-main-static // main 主应用静态文件目录
-react
-react-static // react 子应用静态文件目录
-vue
-vue-static // vue 子应用静态文件目录
-main-server.js // main 主应用服务器
-vue-server.js // vue 子应用服务器
-react-server.js // react 子应用服务器
Я загрузил код этого микроинтерфейса вgithub, рекомендуется клонировать проект и читать вместе с этой главой, эффект лучше. Давайте посмотрим на бегущий эффект DEMO:
резюме
Для разработки и обслуживания крупномасштабных приложений использование микроинтерфейсов может облегчить нам задачу. Однако, если это небольшое приложение, рекомендуется создать отдельный проект для разработки. В конце концов, микро-фронтенды также несут дополнительные затраты на разработку и обслуживание.
использованная литература
- Microservices
- Вероятно, самое полное решение для микроинтерфейса, которое вы когда-либо видели.
- qiankun
Позвольте вам начать работу с фронтенд-инжинирингомПолнотекстовый каталог:
- Выбор технологии: как сделать выбор технологии?
- Единообразные нормы: как сформулировать нормы и использовать инструменты для обеспечения их строгого соблюдения?
- Компонентизация интерфейса: что такое модульность и компонентизация?
- Тестирование: как писать модульные тесты и E2E (сквозные) тесты?
- Инструменты сборки: что такое инструменты сборки? Каковы особенности и преимущества?
- Автоматическое развертывание: как использовать Jenkins, Github Actions для автоматизации развертывания проектов?
- Интерфейсный мониторинг: объясните принцип внешнего мониторинга и как использовать sentry для мониторинга проекта.
- Оптимизация производительности (1): как определить производительность веб-сайта? Каковы некоторые практические правила оптимизации производительности?
- Оптимизация производительности (2): как определить производительность сайта? Каковы некоторые практические правила оптимизации производительности?
- Рефакторинг: зачем делать рефакторинг? Какие существуют методы рефакторинга?
- Микросервисы: что такое микросервисы? Как построить проект микросервиса?
- Serverless: что такое Serverless и как использовать Serverless?