BFF模式:为什么你的SPA需要一个”后端代理人”?

引言:前端直接调用API的痛点
在传统的SPA架构中,前端直接调用后端API:
1 2 3
| React App → 用户服务API → 订单服务API → 支付服务API
|
这种架构有几个问题:
- 安全问题:前端需要处理Token
- 性能问题:多次API调用
- 耦合问题:前端和后端紧密耦合
今天,我用白话讲讲BFF模式如何解决这些问题。
第一章:BFF是什么?
一句话解释
BFF(Backend For Frontend)就是:为前端专门定制的后端服务。
生活中的例子
没有BFF的世界:
- 你去银行办业务
- 要分别去柜台、理财、贷款窗口
- 每个窗口都要排队
- 流程复杂,效率低
有BFF的世界:
- 你去银行办业务
- 有一个专属客户经理
- 客户经理帮你搞定所有事情
- 你只需要和一个人沟通
BFF就像这个专属客户经理:前端只需要和BFF沟通,BFF负责和后端服务打交道。
第二章:BFF的工作原理

架构对比
传统架构:
1 2 3
| SPA → 用户服务 → 订单服务 → 支付服务
|
BFF架构:
1 2 3
| SPA → BFF → 用户服务 → 订单服务 → 支付服务
|
工作流程
获取用户订单列表:
传统架构:
1 2 3 4
| 1. SPA调用用户服务,获取用户信息 2. SPA调用订单服务,获取订单列表 3. SPA调用支付服务,获取支付状态 4. SPA组装数据,显示页面
|
BFF架构:
1 2 3 4 5 6
| 1. SPA调用BFF,获取用户订单列表 2. BFF调用用户服务,获取用户信息 3. BFF调用订单服务,获取订单列表 4. BFF调用支付服务,获取支付状态 5. BFF组装数据,返回给SPA 6. SPA显示页面
|
代码示例
BFF服务(Node.js):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| const express = require('express') const axios = require('axios') const app = express()
app.get('/api/user-orders', async (req, res) => { const userId = req.user.id const [user, orders, payments] = await Promise.all([ axios.get(`http://user-service/users/${userId}`), axios.get(`http://order-service/orders?userId=${userId}`), axios.get(`http://payment-service/payments?userId=${userId}`) ]) const result = { user: user.data, orders: orders.data.map(order => ({ ...order, payment: payments.data.find(p => p.orderId === order.id) })) } res.json(result) })
app.listen(3000)
|
SPA调用:
1 2 3 4 5 6 7
| const response = await fetch('/api/user-orders') const data = await response.json()
console.log(data.user) console.log(data.orders)
|
第三章:BFF的优势
优势一:安全性
问题:前端直接调用API,需要处理Token。
BFF解决方案:
- Token存储在BFF(服务端)
- 前端不需要接触Token
- 安全性大大提高
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| app.use((req, res, next) => { const token = req.cookies.access_token const user = verifyToken(token) if (!user) { return res.status(401).json({ error: 'Unauthorized' }) } req.user = user next() })
|
优势二:性能
问题:前端多次调用API,网络开销大。
BFF解决方案:
- BFF聚合多个API调用
- 前端只需要一次请求
- 减少网络开销
性能对比:
| 场景 |
传统架构 |
BFF架构 |
| API调用次数 |
3次 |
1次 |
| 网络延迟 |
300ms |
100ms |
| 数据传输 |
大 |
小 |
优势三:解耦
问题:前端和后端紧密耦合,修改困难。
BFF解决方案:
- 前端只依赖BFF
- 后端服务可以独立演进
- 修改不影响前端
示例:
1 2 3 4 5
| 用户服务从v1升级到v2 ↓ 只需要修改BFF的调用逻辑 ↓ SPA代码不需要修改
|
优势四:灵活性
问题:不同前端需要不同的数据格式。
BFF解决方案:
- 每个前端有专属BFF
- BFF根据前端需求定制数据
- 灵活适配不同前端
示例:
1 2 3
| Web BFF → 返回完整数据 Mobile BFF → 返回精简数据 TV BFF → 返回特定格式
|
第四章:BFF的实现
技术选型
| 技术 |
优点 |
缺点 |
| Node.js |
轻量、异步 |
不适合CPU密集型 |
| Go |
高性能、并发 |
学习曲线陡 |
| Java |
成熟、稳定 |
相对重量级 |
| Python |
简单、易学 |
性能一般 |
实现步骤
步骤一:创建BFF服务
1 2 3 4 5 6 7 8 9
| { "name": "bff-service", "dependencies": { "express": "^4.18.0", "axios": "^1.4.0", "cookie-parser": "^1.4.6" } }
|
步骤二:实现认证中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const jwt = require('jsonwebtoken')
const authMiddleware = (req, res, next) => { const token = req.cookies.access_token if (!token) { return res.status(401).json({ error: 'No token provided' }) } try { const decoded = jwt.verify(token, process.env.JWT_SECRET) req.user = decoded next() } catch (error) { return res.status(401).json({ error: 'Invalid token' }) } }
module.exports = authMiddleware
|
步骤三:实现API聚合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const express = require('express') const axios = require('axios') const router = express.Router()
router.get('/profile', async (req, res) => { const userId = req.user.id try { const [user, orders, preferences] = await Promise.all([ axios.get(`http://user-service/users/${userId}`), axios.get(`http://order-service/orders?userId=${userId}&limit=5`), axios.get(`http://preference-service/preferences/${userId}`) ]) res.json({ user: user.data, recentOrders: orders.data, preferences: preferences.data }) } catch (error) { res.status(500).json({ error: 'Failed to fetch profile' }) } })
module.exports = router
|
步骤四:配置CORS和Cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const express = require('express') const cors = require('cors') const cookieParser = require('cookie-parser')
const app = express()
app.use(cors({ origin: 'http://localhost:3000', credentials: true }))
app.use(cookieParser())
app.use('/api', require('./routes/user')) app.use('/api', require('./routes/order'))
app.listen(3001)
|
第五章:BFF的挑战
挑战一:额外的复杂性
问题:需要维护额外的BFF服务。
解决方案:
挑战二:单点故障
问题:BFF成为单点故障。
解决方案:
挑战三:性能开销
问题:额外的网络跳转。
解决方案:
第六章:BFF vs API Gateway
概念对比
| 维度 |
BFF |
API Gateway |
| 定位 |
为前端定制 |
统一入口 |
| 粒度 |
细粒度 |
粗粒度 |
| 数量 |
每个前端一个 |
通常一个 |
| 功能 |
API聚合、认证 |
路由、限流、监控 |
架构对比
BFF架构:
1 2
| Web SPA → Web BFF → 微服务 Mobile App → Mobile BFF → 微服务
|
API Gateway架构:
1 2
| Web SPA → API Gateway → 微服务 Mobile App → API Gateway → 微服务
|
混合架构:
1 2
| Web SPA → BFF → API Gateway → 微服务 Mobile App → BFF → API Gateway → 微服务
|
总结
核心要点
- BFF是为前端定制的后端服务
- 解决安全、性能、耦合问题
- 每个前端可以有专属BFF
- 需要额外的维护成本
适用场景
- SPA应用:需要安全处理Token
- 多端应用:Web、Mobile、TV等
- 微服务架构:需要API聚合
- 性能敏感:需要减少API调用
给开发者的建议
- 评估是否需要BFF:不是所有项目都需要
- 选择合适的技术栈:根据团队技能选择
- 自动化部署:减少维护成本
- 监控和日志:及时发现问题
BFF不是银弹,但在合适场景下,它是解决SPA认证和API聚合问题的利器。