一、引言
当你的应用同时服务 Web、移动端、第三方开放接口时,直接把后端微服务暴露给每个端会陷入噩梦:每个端都要拼多个接口、每个端都要自己做鉴权、每个端看到的字段都不一样。API 网关(统一入口)+ BFF(每端适配层)就是解决「接口碎片化」的架构范式。
本文拆解四层:网关的核心职责、BFF 与网关怎么分工、边缘聚合怎么减少请求、接口编排与错误处理怎么做,最后给出从单体 API 演进到网关 + BFF 的路径。
二、网关的核心职责:统一入口
2.1 网关的分层
客户端 → API 网关(统一入口)
├── 路由:按 path 分发到各服务
├── 鉴权:统一校验 token(一次认证,全局生效)
├── 限流:按用户/密钥/路径配额
├── 安全:CORS、请求体校验、注入防护
└── 聚合:把多服务响应拼成一个(可选,见 BFF)
→ 后端服务(user / order / search ...)
| 职责 | 说明 | 典型实现 |
|---|---|---|
| 路由 | path → 服务映射 | 网关配置 / 边缘函数 |
| 鉴权 | 统一 token 校验 | JWT 验证 / session 查 KV |
| 限流 | 防滥用 | 按 IP / 用户 / 密钥计数 |
| CORS | 跨域策略 | 网关统一配置 |
| 协议转换 | REST ↔ 内部 | 网关做适配 |
2.2 网关 ≠ 业务聚合
网关适合做的:路由、鉴权、限流、CORS(横切关注点)
网关不该做的:业务拼接、字段裁剪(这是 BFF 的活)
心法:网关管「切面」,BFF 管「适配」。把业务拼接塞进网关会让网关变成巨型单体,难以维护。
三、BFF:每端的「翻译官」
3.1 BFF 是什么
BFF = Backend for Frontend
└── 每类客户端一个「专属后端」
├── Web BFF:按浏览器需要聚合字段
├── 移动 BFF:按 App 需要精简 payload
└── 第三方 API:独立接口契约
| 问题 | BFF 解决 |
|---|---|
| 首屏要 8 个请求 | BFF 聚合为 1 个 |
| Web 要的字段移动端不要 | 各端 BFF 裁剪 |
| 端上做业务判断难 | BFF 服务端拼好 |
| 接口版本混乱 | BFF 内适配版本 |
3.2 BFF 的两种形态
形态 A:网关 + 通用聚合层
└── 一个聚合层,按「端标识」返回不同结构
形态 B:每端独立 BFF 服务
└── web-bff / mobile-bff / partner-api 各一个部署
| 维度 | 单聚合层 | 多 BFF |
|---|---|---|
| 维护 | 一套代码 | 每端一套 |
| 隔离 | 弱(共用) | 强 |
| 演化 | 快 | 慢但稳 |
| 适用 | 端差异小 | 端差异大 |
四、边缘聚合:减少首屏请求
4.1 串行 vs 并行聚合
客户端(无聚合):首页 = 用户信息 + 推荐 + 通知 + 配置(4 个请求)
BFF 聚合:BFF 内并行调 4 个服务 → 返回 1 个响应
// Next.js Route Handler 做 BFF 聚合
export async function GET(req: Request) {
const [user, feed, notif, config] = await Promise.all([
fetch(API + '/user', { headers: { auth } }),
fetch(API + '/feed', { headers: { auth } }),
fetch(API + '/notifications', { headers: { auth } }),
fetch(API + '/config'),
])
return Response.json({ user, feed, notif, config })
}
4.2 边缘聚合的收益
4 个请求 → 1 个请求
首屏:4 × RTT → 1 × RTT(网络往返大幅减少)
鉴权:4 次验签 → 1 次
失败处理:集中一处,不必 4 个端各自处理
心法:聚合放边缘(CDN 层 / 边缘函数)比放中心更有效——请求在最近节点聚合,RTT 缩短最明显。
五、接口编排与错误处理
5.1 编排:串行依赖
部分聚合有依赖:先拿用户 → 再按用户拿订单 → 再按订单拿详情。
// 有依赖的编排:串行 + 提前失败
const user = await fetch(API + '/user')
const orders = await fetch(API + `/user/${user.id}/orders`)
const details = await Promise.all(
orders.map(o => fetch(API + `/order/${o.id}/details`))
)
5.2 错误聚合
部分成功(user 成功、feed 失败):
返回 200 + 结构里的 error 标记,让前端「降级渲染」
而非整体 500(那样整页失败)
const results = await Promise.allSettled([fetchUser(), fetchFeed()])
return Response.json({
user: results[0].status === 'fulfilled' ? results[0].value : null,
feed: results[1].status === 'fulfilled' ? results[1].value : { error: 'feed down' },
})
| 策略 | 场景 |
|---|---|
| 整体失败 | 强依赖(user 拿不到就别继续) |
| 部分降级 | 弱依赖(feed 挂了首页仍可看) |
铁律:BFF 要按依赖强弱区分失败策略。强依赖失败整体返回错误,弱依赖失败返回空 + 降级标记,别让一个下游拖垮整个聚合。
六、多端适配:Web / 移动 / 第三方
6.1 统一入口 + 端差异
统一网关入口:/api/*
按端适配:
├── UA / header 识别端
├── 版本 header(x-api-version)
└── 各端 BFF 返回不同字段
6.2 三方开放接口
第三方 API 是「独立契约」:
- 独立鉴权(API Key / OAuth)
- 独立限流(按 key 配额)
- 独立版本(稳定契约,不能随便改)
- 文档化(OpenAPI)
边界:第三方 API 要当作「独立产品」维护,不能和 Web 内部接口共用可变逻辑。它的稳定性优先级最高。
七、迁移路径:从单体到网关 + BFF
阶段 1:单体 API(一个服务全暴露)
阶段 2:加网关(路由/鉴权/限流抽出来)
阶段 3:加 BFF(按端聚合,客户端告别拼接口)
阶段 4:后端拆微服务(网关 + BFF 已就位,后端拆分无感)
| 阶段 | 收益 | 风险 |
|---|---|---|
| 1→2 | 鉴权统一、限流就位 | 网关单点 |
| 2→3 | 首屏加速、端适配 | BFF 增代码 |
| 3→4 | 服务自治 | 链路变长 |
心法:先把网关 + BFF 铺好,再拆后端微服务。网关做横切、BFF 做适配,后端拆成什么都对客户端无感——这是最平滑的演进顺序。
八、总结
API 网关 + BFF 的核心是「一个入口,多端适配」:
- 网关管切面:路由、鉴权、限流、CORS,一次配置全局生效。
- BFF 管适配:每端一个翻译官,聚合 + 裁剪 + 版本。
- 边缘聚合:首屏 4 请求 → 1 请求,RTT 大幅缩短。
- 依赖分级:强依赖整体失败,弱依赖降级返回。
- 迁移有序:先网关再 BFF,最后才是后端拆微服务。
把「接口碎片化」当成架构问题而非「再加一个接口」来解决,配合 边缘认证 的统一鉴权与 可观测性 的链路追踪,多端架构就能既快又稳。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。