做后台系统别再只会单体架构了,微前端才是更优解

1,627 阅读10分钟

在后台系统开发领域,传统的单体架构已经无法满足现代企业的复杂需求。本文将深入探讨为什么微前端架构是后台系统的更优选择,并通过开源项目 PbstarAdmin 的完整实践案例,展示如何构建高性能、可扩展的微前端后台系统。

前言:单体架构的那些坑

说实话,做后台系统开发这么多年,单体架构的痛点真是深有体会:

  • 代码耦合严重:多个业务模块混杂在一起,修改一个功能可能影响到其他模块
  • 代码无法隔离:所有代码都在一个仓库中,无法进行物理隔离,权限管理困难
  • 构建速度缓慢:随着业务增长,项目体积越来越大,构建时间从几分钟到十几分钟不等
  • 技术栈锁定:整个项目被限制在单一技术栈,无法灵活选择最适合的技术方案
  • 团队协作困难:多人同时开发时,代码冲突频发,发布需要协调所有模块
  • 部署风险高:任何小改动都需要重新部署整个应用,风险巨大

这些问题的根源在于传统的单体架构模式。在单体架构中,所有的业务功能都被打包在一个巨大的代码库中,就像一个臃肿的巨人,行动迟缓且容易摔倒。

微前端:一种可行的解决方案

微前端(Micro Frontends)把前端应用拆分成多个小型、独立的部分。每个部分都能独立开发、测试、部署,最后组合成一个完整的应用。

这样做的好处很明显:

  • 独立部署:改一个模块不用重新发布整个系统
  • 团队自治:每个团队管好自己的模块就行
  • 渐进迁移:不用一次性重写,可以慢慢来
  • 故障隔离:一个模块崩了不会拖垮整个应用

PbstarAdmin:微前端架构的实践案例

为了让大家更好地理解和实践微前端架构,我们开源了一个完整的微前端后台系统项目 —— PbstarAdmin。这个项目基于腾讯的 wujie 微前端框架,提供了一套经过实际业务验证的完整解决方案。

项目特色

  • 微前端架构:基于 wujie 框架,支持动态加载子应用,实现真正的模块隔离
  • 双重隔离机制:Git子模块 + Rsbuild构建,支持内外部子应用混合管理
  • 模块化设计:pnpm monorepo 管理,代码组织清晰,依赖管理高效
  • 组件复用:共享组件库,统一别名引用,提升开发效率
  • 工程化工具:完整的 Ptools 工具链,从创建到构建全流程自动化
  • 高性能构建:基于 Rsbuild,支持多环境配置,构建速度快

微后台架构图

graph TB
    Main["主应用<br/>wujie框架<br/>路由管理 | 应用注册 | 状态共享"]

    Main --> App1["内部子应用<br/>主仓库内<br/>独立构建部署"]
    Main --> App2["外部子应用<br/>Git子模块<br/>独立构建部署"]

    App1 --> Shared["共享资源层<br/>组件库 | 工具函数<br/>公共样式 | 图标资源"]
    App2 --> Shared

    style Main fill:#e1f5ff,stroke:#01579b,stroke-width:2px
    style App1 fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
    style App2 fill:#fff3e0,stroke:#e65100,stroke-width:2px
    style Shared fill:#e8f5e9,stroke:#1b5e20,stroke-width:2px

技术选型

技术选型比较务实,都是现在主流的方案:

  • 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)])),
  },
});

Pcomponents 共享组件库

Pcomponents 是项目的共享组件库,通过 @Pcomponents 别名统一引用,实现跨应用组件复用。

组件库结构

components/
├── base/                      # 基础组件
│   ├── p-button/              # 按钮组件
│   ├── p-dialog/              # 对话框组件
│   ├── p-table/               # 表格组件
│   ├── p-search/              # 搜索组件
│   ├── p-title/               # 标题组件
│   ├── p-item/                # 表单项组件
│   ├── p-icon/                # 图标组件
│   └── p-collapse/            # 折叠面板组件
├── layout/                    # 布局组件
│   └── p-twinBox/             # 双栏布局组件
├── more/                      # 高级组件
│   ├── p-codeView/            # 代码查看器
│   ├── p-iconSelect/          # 图标选择器
│   └── p-verificationCode/    # 验证码组件
├── page/                      # 页面级组件
│   ├── 403.vue                # 403 错误页
│   └── 404.vue                # 404 错误页
└── index.js                   # 组件统一导出

使用方式

1. 在 rsbuild.config.mjs 中配置别名:

resolve: {
  alias: {
    "@Pcomponents": "./components",
    "@Passets": "./assets",
  },
}

2. 在子应用中按需引入:

// 引入多个组件
import { pTable, pDialog, pButton, pItem } from "@Pcomponents";

// 引入单个组件
import { pIcon } from "@Pcomponents";

3. 使用示例:

<template>
  <div>
    <p-title title="用户管理" />
    <p-search :options="searchOptions" @search="handleSearch" />
    <p-table :data="tableData" :columns="columns" @row-click="handleRowClick" />
    <p-dialog v-model="dialogVisible" title="编辑用户">
      <p-item label="用户名" prop="username">
        <el-input v-model="form.username" />
      </p-item>
    </p-dialog>
  </div>
</template>

<script setup>
import { pTitle, pSearch, pTable, pDialog, pItem } from "@Pcomponents";

const searchOptions = [
  { label: "用户名", prop: "username", type: "input" },
  { label: "状态", prop: "status", type: "select" },
];

const columns = [
  { label: "用户名", prop: "username" },
  { label: "邮箱", prop: "email" },
  { label: "状态", prop: "status" },
];
</script>

组件特点

1. 统一的设计风格

  • 基于 Element Plus 二次封装
  • 统一的 UI 规范和交互体验
  • 符合后台系统的业务场景

2. 高度可配置

  • 通过 props 灵活配置组件行为
  • 提供丰富的插槽支持定制化
  • 支持事件回调扩展功能

3. 开箱即用

  • 封装了常见的业务逻辑
  • 减少重复代码,提升开发效率
  • 降低学习成本,新手友好

4. 跨应用复用

  • 通过 @Pcomponents 别名在所有子应用中统一引用
  • monorepo 管理,修改后所有应用自动更新
  • 避免组件重复开发和维护成本

代码隔离的双重机制

传统方案的局限性

之前用过一些微前端方案,发现隔离做得并不好:

  • 代码都在一起:所有子应用代码混在一个仓库,权限控制很麻烦
  • 依赖经常冲突:这个子应用要Vue3,那个要Vue2,构建时各种问题
  • 构建互相影响:一个子应用构建失败了,整个项目都跑不起来
  • 版本管理混乱:没法单独给某个业务模块打版本标签

Git子模块 + Rsbuild构建的双重隔离

采用 Git子模块 + Rsbuild构建 的双重隔离,把代码隔离做到了物理层面。

1. Git子模块隔离

# .gitmodules 配置
[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"],
      }),
    ],
  };
};

3. 实际效果

权限管理

  • 仓库级别权限:外部子应用可以单独设置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"  // 版本不一样,但不会冲突
  }
}

故障隔离

// 子应用A构建出错了,不会影响子应用B
const appConfigs = {
  system: createAppConfig({ key: "system" }), // ✅ 正常构建
  equipment: createAppConfig({ key: "equipment" }), // ❌ 构建失败
  example: createAppConfig({ key: "example" }), // ✅ 不受影响
};

Ptools 工具链

Ptools 是项目内置的 CLI 工具,通过交互式命令简化开发流程,避免手动配置复杂的参数。

为什么需要 Ptools

直接使用构建命令的问题

# 需要记住复杂的命令和参数
rsbuild build --environment equipment --port 8803

# 容易敲错,还得手动指定端口和环境
rsbuild dev --environment system --port 8801

核心命令

# 启动开发环境 - 交互式选择
pnpm run dev

# 构建指定子应用
pnpm run build

# 创建新的子应用 - 引导式创建
pnpm run create

# 添加/移除依赖包 - 精确到具体工程
pnpm run add
pnpm run remove

使用示例

# 开发环境 - 交互式选择子应用
$ 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

实现示例

// tools/cli/build.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);
    }
  });

优势特点

  1. 不用记配置:开发者不用了解底层的Rsbuild配置
  2. 不会选错:自动发现可用的子应用,避免手打错误
  3. 统一入口:所有操作都通过统一的CLI,比较好记
  4. 减少出错:内置参数验证和错误处理
  5. 流程统一:确保团队成员用相同的流程

子应用创建流程

// 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 clone https://github.com/pbstar/pbstar-admin.git

# 进入项目目录
cd pbstar-admin

# 克隆外部子应用仓库(可选)
git submodule update --init

# 安装依赖
pnpm install

# 使用Ptools启动开发环境
pnpm run dev
# 交互式选择要启动的子应用

# 构建项目
pnpm run build
# 选择要构建的子应用

# 创建新的子应用
pnpm run create

总结

单体架构就像一艘巨大的航空母舰,虽然功能强大,但转向困难,维护成本高。而微前端架构就像一支现代化的舰队,每艘舰艇都有自己的使命,既能独立作战,又能协同配合。

通过微前端实践,我发现在后台系统中的优势确实很明显:

  • 开发效率:构建速度快了很多,热更新基本是秒级
  • 部署运维:可以独立部署,不用每次都全量发布
  • 团队协作:各团队负责自己的模块,冲突少了很多
  • 系统稳定性:一个模块出问题不会拖垮整个系统

当然,微前端也有它的复杂性,比如通信机制、状态同步等问题。但总的来说,对于大型后台系统,微前端是一个值得考虑的方向。

如果你也在做后台系统,建议可以试试看微前端的思路,或许能解决你当前遇到的一些痛点。

相关资料


💡 这里是初辰,一个有理想的切图仔!

🎉 如果本文对你有帮助,别忘了点赞、收藏、评论哦!

⭐ 最后,别忘了 给项目点个Star 哦!