微前端和微服务到底怎么搭?我拿一个电商平台把前后端融合架构跑通了
微前端和微服务到底怎么搭?我拿一个电商平台把前后端融合架构跑通了
导语
2026 年,很多团队前端用了 Module Federation 或 Qiankun,后端拆了微服务,但两者各玩各的——前端按页面拆、后端按业务拆,中间的 API 网关要么成了一层”透传壳”,要么就变成了巨大的胶水代码,两边改一个字段要协调两天。微前端和微服务到底该怎么融合,才能让团队真正受益?
问题:微前端和微服务各管各的,哪里出了问题?
一个典型的电商平台,前端拆成了首页、商品、购物车、订单、支付五个子应用,后端按业务也拆成了用户服务、商品服务、订单服务、支付服务。看似完美的”前后双微”架构,实际跑起来全是坑。
问题 1:子应用不知道该调哪个后端
默认做法是每个子应用独立部署,各自在代码里写死后端 API 地址。结果商品前端调用商品服务网关,购物车前端调购物车服务网关——但购物车页面需要同时展示商品信息、优惠信息、库存信息,一个页面要调三个服务,前端要做三次请求拼数据。
问题 2:认证在各子应用里重复实现
每个子应用都得自己配 JWT 校验、登录态检查。用户服务改了一次 Token 格式,五个子应用全要改,漏一个就 401 修半天。
问题 3:部署节奏打架
后端改了个接口,前端三个子应用都要跟着适配。但前端子应用和后端微服务的 CI/CD 管道不打通,前端改完等后端上线,后端上线又等前端发版,两边互相卡脖子。
这三个问题的根因只有一个:前后端虽然都拆了,但拆的逻辑没对齐,治理体系没打通。
方案一:对齐拆分粒度,给每个子应用配一个 BFF
如果你问微前端和微服务融合的第一原则是什么,我会说是:一个前端子应用对应一个后端 BFF(Backend For Frontend),而不是直接调多个微服务。
什么意思?商品管理前端不要直接调商品服务、库存服务、价格服务。而是建一个商品 BFF,它负责聚合这三个服务的数据,返回商品页面需要的完整视图模型。
// ❌ 错:商品前端自己拼数据
// product-app/src/pages/Detail.tsx
const product = await fetch('/api/product/1001');
const stock = await fetch('/api/inventory/1001');
const price = await fetch('/api/pricing/1001');
// 前端做聚合,网络开销大、容错难
// ✅ 对:BFF 帮你拼好
// product-bff/src/routes/product.ts
router.get('/api/bff/product/:id', async (req, res) => {
const [product, stock, price] = await Promise.all([
productService.getById(req.params.id),
inventoryService.getByProductId(req.params.id),
pricingService.getByProductId(req.params.id),
]);
res.json({
...product,
stock: stock.available,
price: price.current,
promotion: price.promotion || null,
});
});
前端只需调 BFF 一个接口,BFF 负责聚合、降级、缓存。这样前端子应用只和它的 BFF 双向绑定,后端改了微服务,只要 BFF 的输出契约不变,前端不用动。
这样做的好处:
- 前端子应用和 BFF 可以共享同一个 Git 仓库、同一个 CI/CD 管道
- 前后端可以按”垂直业务”独立迭代(商品团队管商品前端 + 商品 BFF + 商品微服务)
- BFF 层可以做数据裁剪,前端不用接收不需要的字段
方案二:统一网关层,把认证、路由、限流下沉
有了 BFF,你还需要一个统一的 API 网关来承接所有子应用的流量入口。网关层的职责:
1. 统一认证鉴权:所有的 JWT/OAuth 校验在网关层完成,子应用和 BFF 不需要自己处理
2. 请求路由:根据 URL 前缀把请求分发到对应的 BFF
3. 限流熔断:按子应用粒度做限流,一个子应用出流量洪峰不影响其他
# gateway/routes.yaml (以 Spring Cloud Gateway 为例)
spring:
cloud:
gateway:
routes:
- id: product-bff
uri: lb://product-bff
predicates:
- Path=/api/bff/product/**
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@userKeyResolver}'
redis-rate-limiter.replenishRate: 100
redis-rate-limiter.burstCapacity: 200
- id: order-bff
uri: lb://order-bff
predicates:
- Path=/api/bff/order/**
# ... 每个子应用一条路由
网关层干掉了重复的认证代码。所有子应用统一从请求头拿 x-user-id 和 x-user-roles,网关已经校验好 Token 并注入进去了。
// 任意子应用的 BFF 中,直接信任网关注入的 Header
const userId = req.headers['x-user-id'];
// 不再需要自己解析 JWT
方案三:打通 CI/CD,让垂直团队真正自治
架构变了,流程也得变。我给你看我们最终推的 CI/CD 方案:
电商平台仓库结构
├── apps/
│ ├── home/ # 首页子应用(前端 + BFF)
│ │ ├── frontend/
│ │ ├── bff/
│ │ └── .ci.yml
│ ├── product/ # 商品子应用(前端 + BFF + 部分微服务)
│ │ ├── frontend/
│ │ ├── bff/
│ │ ├── product-service/
│ │ └── .ci.yml
│ ├── order/
│ └── payment/
├── shared/ # 共享库
│ ├── ui-components/
│ └── api-client/
└── gateway/ # API 网关配置
每个子应用目录里放一个 .ci.yml,独立触发部署:
# apps/product/.ci.yml
name: product-ci
on:
push:
paths:
- 'apps/product/**'
jobs:
test:
steps:
- run: cd apps/product && pnpm test
deploy-bff:
steps:
- run: cd apps/product/bff && docker build -t product-bff .
- run: kubectl rollout restart deployment/product-bff
deploy-frontend:
steps:
- run: cd apps/product/frontend && pnpm build
- run: aws s3 sync dist/ s3://cdn/product/
关键改动:路径过滤,只有 product 目录改动了才触发 product 的 CI/CD。商品团队改了自己的代码,不会触发首页或订单的部署。
方案四:事件驱动让前后端解耦
还有个常见场景:后端改了库存,前端商品详情页怎么实时更新?
传统的做法是前端轮询,或者 BFF 主动查询。但在融合架构里,我们推荐用事件驱动:
// 库存服务 → 发出事件
eventBus.publish('inventory.changed', {
productId: '1001',
available: 5,
});
// 商品 BFF → 订阅事件,更新本地缓存
eventBus.subscribe('inventory.changed', (event) => {
cache.set(`product:${event.productId}:stock`, event.available);
});
BFF 层同时承担了数据聚合和缓存代理的职责。库存变了,BFF 的缓存失效,下一次请求重新聚合新数据。前端完全感知不到,只管调 BFF。
收尾
总结一下微前端和微服务融合的四条原则:
1. BFF 是粘合剂:每个前端子应用配一个 BFF,数据聚合下沉到 BFF 层,前端只管展示
2. 网关是门卫:认证、路由、限流全部提网关层,子应用零重复
3. 垂直自治是目标:一个前端子应用 + 它的 BFF + 对应的微服务,由一个团队全权负责
4. 事件驱动是辅路:跨服务的数据变更通过事件总线推动,不用轮询,不用硬编码
如果你现在正打算搭”双微”架构,或者已经在里面踩坑了,建议先从对齐拆分粒度 + 加一层 BFF开始。把 BFF 层搭好后,网关和 CI/CD 改造就是水到渠成的事,而不是硬推全量改造。
要验证这套方案是否适合你团队,就看一个问题:你的前端子应用改一行代码,需不需要等后端也发版? 如果答案是”需要”,那融合架构就还没到位。
评论区
登录后可评论。












