做后台系统别再只会单体架构了,微前端才是更优解
在后台系统开发领域,传统的单体架构已经无法满足现代企业的复杂需求。本文将深入探讨为什么微前端架构是后台系统的更优选择,并通过一个完整的开源项目案例,展示如何构建高性能、可扩展的微前端后台系统。
前言:单体架构的那些坑
说实话,做后台系统开发这么多年,单体架构的痛点真是深有体会:
- 代码耦合严重:多个业务模块混杂在一起,修改一个功能可能影响到其他模块
- 代码无法隔离:所有代码都在一个仓库中,无法进行物理隔离,权限管理困难
- 构建速度缓慢:随着业务增长,项目体积越来越大,构建时间从几分钟到十几分钟不等
- 技术栈锁定:整个项目被限制在单一技术栈,无法灵活选择最适合的技术方案
- 团队协作困难:多人同时开发时,代码冲突频发,发布需要协调所有模块
- 部署风险高:任何小改动都需要重新部署整个应用,风险巨大
这些问题的根源在于传统的单体架构模式。在单体架构中,所有的业务功能都被打包在一个巨大的代码库中,就像一个臃肿的巨人,行动迟缓且容易摔倒。
微前端:一种可行的解决方案
微前端(Micro Frontends)把前端应用拆分成多个小型、独立的部分。每个部分都能独立开发、测试、部署,最后组合成一个完整的应用。
这样做的好处很明显:
- 独立部署:改一个模块不用重新发布整个系统
- 团队自治:每个团队管好自己的模块就行
- 渐进迁移:不用一次性重写,可以慢慢来
- 故障隔离:一个模块崩了不会拖垮整个应用
一个实际案例:PbstarAdmin
为了让大家看看微前端在后台系统中怎么用,我分享一下最近做的一个项目 —— PbstarAdmin。
这个项目用到了腾讯的 wujie 微前端框架,解决了一些实际开发中的痛点。
代码隔离这块是怎么做的?
PbstarAdmin 用了一个比较实用的办法:Git子模块 + Rsbuild构建 双重隔离。
Git子模块隔离
简单说就是把代码分成两类:
-
内部子应用:放在主仓库的
apps/目录下,适合核心业务,改起来方便 - 外部子应用:用Git子模块管理,完全独立的仓库,适合第三方模块或者需要权限控制的代码
Rsbuild构建隔离
每个子应用都有自己的构建配置:
- 独立的构建配置和输出目录
- 子应用之间没有依赖耦合
- 可以独立部署和版本管理
这样做的好处是实实在在的:不同团队负责不同模块,互不干扰;出问题也容易定位。
项目特色
PbstarAdmin 这个项目主要解决了几个实际问题:
- 微前端架构:用腾讯 wujie 框架,支持动态加载子应用
- 模块化设计:pnpm monorepo 管理,支持内外部子应用
- 组件复用:共享组件库,统一别名引用
- 工程化工具:CLI 工具链简化开发流程
- 高性能构建:基于 Rsbuild,支持多环境配置
技术选型
技术选型比较务实,都是现在主流的方案:
- Vue 3: Composition API 开发体验不错
- Pinia:状态管理比 Vuex 简洁
- Element Plus:组件库成熟稳定
- Rsbuild:基于 Rspack,构建速度很快
- pnpm:monorepo 管理很方便
- wujie:腾讯的微前端方案,相对成熟
架构设计
项目结构
整个项目结构比较清晰:
pbstar-admin/
├── main/ # 主应用(基座)
├── apps/ # 子应用目录
│ ├── app-common/ # 公共子应用模块
│ ├── system/ # 系统管理应用
│ ├── example/ # 示例应用
│ ├── equipment/ # 设备管理应用(外部子应用)
│ └── apps.json # 子应用配置
├── components/ # 共享组件库
├── assets/ # 共享资源
└── tools/ # 工具模块(CLI)
微应用配置
在 apps/apps.json 中配置各个微应用的信息:
[
{
"key": "system",
"devPort": 8801,
"proUrl": "http://pbstar-admin-system.pbstar.cn/"
},
{
"key": "example",
"devPort": 8802,
"proUrl": "http://pbstar-admin-example.pbstar.cn/"
},
{
"key": "equipment",
"devPort": 8803,
"proUrl": "http://pbstar-admin-equipment.pbstar.cn/"
}
]
主应用核心代码
主应用负责整体布局、导航菜单管理和微应用加载:
// main/src/stores/apps.js
export const useAppsStore = defineStore("apps", () => {
const myApps = ref([]); // 存储用户的应用
const appId = ref(0); // 存储当前激活的应用
const setApps = (apps) => {
myApps.value = apps.map((item) => {
return {
id: item.id,
key: item.key,
name: item.name,
icon: item.icon,
group: item.group,
navs: [],
navsTree: [],
};
});
};
const setAppId = async ({ id, key }) => {
let aId = 0;
if (id) {
aId = id;
} else if (key) {
const app = myApps.value.find((item) => item.key === key);
if (app) aId = app.id;
}
if (aId) {
const navRes = await request.get({
url: "/main/getMyNavListByAppId",
data: { appId: aId },
});
if (navRes.code !== 200) {
ElMessage.error("获取应用导航失败!请稍后重试");
return false;
}
setAppNavs(aId, navRes.data);
}
appId.value = aId;
return true;
};
return {
appId,
setApps,
setAppId,
getApp,
getApps,
hasAppNav,
};
});
导航菜单管理
// main/src/components/layout/layout.js
export function useNavMenu() {
const router = useRouter();
const route = useRoute();
const appsStore = useAppsStore();
const activeIndex = ref("1");
const list = ref([]);
const listTree = ref([]);
const updateNavData = () => {
if (appsStore.appId) {
const app = appsStore.getApp();
if (!app) return;
list.value = app.navs;
listTree.value = app.navsTree;
} else {
list.value = [
{
id: 1,
name: "首页",
url: "/admin/pHome",
icon: "el-icon-house",
},
];
listTree.value = list.value;
}
};
const selectNav = (val) => {
activeIndex.value = val;
const url = list.value.find((item) => item.id.toString() === val)?.url;
if (url) {
router.push(url);
}
};
return {
listTree,
activeIndex,
selectNav,
updateNavData,
updateActiveIndex,
};
}
构建配置
使用 Rsbuild 进行高性能构建配置:
// rsbuild.config.mjs
export default defineConfig({
plugins: [pluginVue(), pluginSass(), distZipPlugin()],
output: { legalComments: "none" },
resolve: {
alias: {
"@Pcomponents": "./components",
"@Passets": "./assets",
},
},
server: {
proxy: {
"/api": {
target: import.meta.env.PUBLIC_API_BASE_URL,
pathRewrite: { "^/api": "" },
changeOrigin: true,
},
},
},
environments: {
main: mainConfig,
...Object.fromEntries(apps.map((app) => [app.key, createAppConfig(app)])),
},
});
CLI 工具开发
提供完整的 CLI 工具链,简化开发流程:
// tools/cli/dev.mjs
const list = ["main", ...apps.map((item) => item.key)];
program
.version("1.0.0")
.description("启动应用模块")
.action(async () => {
try {
const answers = await inquirer.prompt([
{
type: "list",
name: "appKey",
message: "请选择要启动的应用模块:",
choices: list,
},
]);
const { appKey } = answers;
// 构建启动命令
let command = "";
if (appKey === "main") {
command = "rsbuild dev --environment main --port 8800 --open";
} else {
const app = apps.find((item) => item.key === appKey);
command = `rsbuild dev --environment ${appKey} --port ${app.devPort}`;
}
execSync(command, { stdio: "inherit", cwd: "../" });
} catch (err) {
console.error(chalk.red("Error:"), err);
process.exit(1);
}
});
代码隔离的终极解决方案
传统方案的局限性
之前用过一些微前端方案,发现隔离做得并不好:
- 代码都在一起:所有子应用代码混在一个仓库,权限控制很麻烦
- 依赖经常冲突:这个子应用要Vue3,那个要Vue2,构建时各种问题
- 构建互相影响:一个子应用构建失败了,整个项目都跑不起来
- 版本管理混乱:没法单独给某个业务模块打版本标签
PbstarAdmin的双重隔离机制
PbstarAdmin 用了 Git子模块 + Rsbuild构建 的双重隔离,算是把代码隔离做到了物理层面。
1. Git子模块隔离
# .gitmodules 配置,其实就是普通的git子模块
[submodule "apps/equipment"]
path = apps/equipment
url = https://github.com/pbstar/pbstar-admin-quipment.git
内部子应用(in类型):
- 代码放在主仓库里,适合核心业务
- 团队协作方便,代码复用容易
- 构建起来也快
外部子应用(out类型):
- 用Git子模块管理,完全独立的仓库
- 适合业务团队或者需要保密的模块
- 版本控制完全独立
2. Rsbuild构建隔离
// rsbuild.config.mjs - 给每个子应用单独的配置
const createAppConfig = (app) => {
const basePath = `./apps/${app.key}`;
return {
source: {
entry: { index: `${basePath}/src/main.js` },
},
output: {
distPath: { root: `./build/dist/${app.key}` },
},
resolve: {
alias: {
"@": basePath + "/src",
},
},
plugins: [
checkUniqueKeyPlugin({
checkPath: `${basePath}/src`,
checkKeys: ["btnkey"],
}),
],
};
};
子应用创建流程
// tools/cli/create.mjs - 智能创建子应用
const answers = await inquirer.prompt([
{
type: "list",
name: "appType",
message: "子应用类型:",
choices: ["in", "out"],
},
{
type: "input",
name: "appKey",
message: "子应用Key:",
validate: (input) => {
if (!/^[a-z0-9-]+$/.test(input)) {
return "子应用Key只能包含小写字母、数字和连字符";
}
return true;
},
},
]);
// 外部子应用就加个git子模块
if (appType === "out" && gitUrl) {
execSync(`git submodule add ${gitUrl} apps/${appKey}`, {
cwd: path.join(__dirname, "../../"),
stdio: "inherit",
});
}
代码隔离的实际效果
用了双重隔离后,确实比传统单体架构方便不少:
权限管理
- 仓库级别权限:外部子应用可以单独设置Git权限
- 代码审查隔离:每个子应用可以有自己的Code Review流程
- 敏感代码保护:核心业务代码可以放在内部子应用中
依赖管理
// 每个子应用可以有自己的依赖,不会冲突
{
"name": "system-subapp",
"dependencies": {
"vue": "^3.5.18",
"element-plus": "^2.10.7",
// 子应用特定的依赖
"echarts": "^5.4.0"
}
}
// 另一个子应用可以用不同版本
{
"name": "equipment-subapp",
"dependencies": {
"vue": "^3.5.18",
"element-plus": "^2.8.0",
"echarts": "^4.9.0" // 版本不一样,但不会冲突
}
}
独立部署
# 用ptools构建指定子应用(推荐)
pnpm run build
# 选择要构建的子应用,比如equipment
Ptools:CLI工具链
PbstarAdmin 还有个特色是 Ptools - 一套CLI工具链,把复杂的构建流程都封装起来了。
Ptools的核心命令
# 启动开发环境 - 会让你选择子应用
pnpm run dev
# 构建指定子应用 - 交互式选择
pnpm run build
# 创建新的子应用 - 引导式创建
pnpm run create
# 添加依赖包 - 精确到具体工程
pnpm run add
# 移除依赖包 - 清理依赖
pnpm run remove
为什么用Ptools而不是直接敲命令
直接敲命令的问题:
# 要记住复杂的命令和参数
rsbuild build --environment equipment --port 8803
# 容易敲错,还得手动指定端口和环境
rsbuild dev --environment system --port 8801
Ptools的交互方式:
// tools/cli/build.mjs - 构建命令其实就是帮你选一下
const list = ["main", ...apps.map((item) => item.key)];
program
.version("1.0.0")
.description("构建应用模块")
.action(async () => {
const answers = await inquirer.prompt([
{
type: "list",
name: "appKey",
message: "请选择要构建的应用模块:",
choices: list, // 自动读取所有可用模块
},
]);
const { appKey } = answers;
// 自动构建正确的环境和配置
const command = `rsbuild build --environment ${appKey}`;
execSync(command, { stdio: "inherit", cwd: "../" });
});
Ptools的好处
- 不用记配置:开发者不用了解底层的Rsbuild配置
- 不会选错:自动发现可用的子应用,避免手打错误
- 统一入口:所有操作都通过统一的CLI,比较好记
- 减少出错:内置参数验证和错误处理
- 流程统一:确保团队成员用相同的流程
依赖管理
// tools/cli/add.mjs - 添加依赖包
const answers = await inquirer.prompt([
{
type: "list",
name: "appKey",
message: "请选择要添加依赖包的工程:",
choices: [
"全局工程",
"assets",
"components",
"tools",
"main",
...apps.map((item) => item.key),
],
},
{
type: "input",
name: "packageName",
message: "请输入要添加的依赖包名称:",
},
{
type: "list",
name: "packageType",
message: "请选择要添加的依赖包类型:",
choices: ["dependencies", "devDependencies"],
},
]);
通过Ptools,PbstarAdmin把从创建到构建的流程都标准化了。
故障隔离
// 子应用A构建出错了,不会影响子应用B
const appConfigs = {
system: createAppConfig({ key: "system" }), // ✅ 正常构建
equipment: createAppConfig({ key: "equipment" }), // ❌ 构建失败
example: createAppConfig({ key: "example" }), // ✅ 不受影响
};
实际使用效果
开发体验
- 独立开发:每个团队可以独立开发自己的微应用,互不干扰
- 构建速度:单个微应用构建比整个项目快很多
- 热更新:修改一个微应用不会影响其他应用,热更新很快
部署运维
- 独立部署:每个微应用可以独立部署,降低风险
- 灰度发布:支持微应用级别的灰度发布
- 故障隔离:单个微应用出错不会影响整个系统
团队协作
- 团队自治:每个团队负责自己的微应用,职责清晰
- 技术选型自由:不同团队可以选择最适合的技术栈
- 并行开发:多个团队可以并行开发,提高效率
快速开始
想要体验这个微前端后台系统?只需要简单的几步:
# 克隆项目
git clone https://github.com/pbstar/pbstar-admin.git
# 进入项目目录
cd pbstar-admin
# 克隆外部子应用仓库(可选)
git submodule update --init
# 安装依赖
pnpm install
# 使用Ptools启动开发环境(推荐方式)
pnpm run dev
# 交互式选择要启动的子应用
# 使用Ptools构建项目
pnpm run build
# 选择要构建的子应用
# 创建新的子应用
pnpm run create
# 添加依赖包
pnpm run add
# 移除依赖包
pnpm run remove
Ptools使用示例
# 开发环境 - 交互式选择子应用
$ pnpm run dev
? 请选择要启动的应用模块: (Use arrow keys)
❯ main
system
example
equipment
# 构建指定子应用
$ pnpm run build
? 请选择要构建的应用模块: (Use arrow keys)
❯ main
system
example
equipment
# 添加依赖包到指定工程
$ pnpm run add
? 请选择要添加依赖包的工程: (Use arrow keys)
❯ 全局工程
assets
components
tools
main
system
example
equipment
? 请输入要添加的依赖包名称: axios
? 请选择要添加的依赖包类型: (Use arrow keys)
❯ dependencies
devDependencies
总结
单体架构就像一搜巨大的航空母舰,虽然功能强大,但转向困难,维护成本高。而微前端架构就像一支现代化的舰队,每艘舰艇都有自己的使命,既能独立作战,又能协同配合。
通过 PbstarAdmin 的实践,我发现微前端在后台系统中的优势确实很明显:
- 开发效率:构建速度快了很多,热更新基本是秒级
- 部署运维:可以独立部署,不用每次都全量发布
- 团队协作:各团队负责自己的模块,冲突少了很多
- 系统稳定性:一个模块出问题不会拖垮整个系统
当然,微前端也有它的复杂性,比如通信机制、状态同步等问题。但总的来说,对于大型后台系统,微前端是一个值得考虑的方向。
如果你也在做后台系统,建议可以试试看微前端的思路,或许能解决你当前遇到的一些痛点。
相关资料
项目地址和文档都整理在这里了,有兴趣的可以看看:
💡 这里是初辰,一个有理想的切图仔!
🎉 如果本文对你有帮助,别忘了点赞、收藏、评论哦!
⭐ 给项目点个Star,支持开源精神,让更多人发现这个优秀的微前端解决方案!