gRPC 网关与 BFF:前后端解耦的正确姿势#
微服务内部用 gRPC(高效、强类型),但前端要的是 REST/JSON。中间这层怎么设计?BFF(Backend For Frontend) 和 网关 是两种答案。这篇文章是我们在 gRPC 微服务化后的对外接口层设计。
一、问题:内部 gRPC,外部要 REST#
二、两种模式#
模式 A:网关(统一入口)#
适用:对外统一入口、跨服务路由、通用横切(鉴权/限流/审计)。
模式 B:BFF(前端专属后端)#
适用:多端(Web/App/小程序)差异大、页面级聚合多、前端体验定制。
三、我的实践:网关 + BFF 分层#
为什么两层都要:
四、BFF 的落地#
gRPC 网关生成(协议转换)#
1
2
3
4
5
6
7
8
9
|
// proto 定义 REST 映射(grpc-gateway 注解)
service ResumeService {
rpc GetResume(GetResumeReq) returns (Resume) {
option (google.api.http) = {
get: "/v1/resumes/{id}"
};
}
}
// 生成:HTTP 处理器 → 转 gRPC 调用(自动完成 REST↔gRPC)
|
BFF 聚合#
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// BFF:一次前端调用,聚合多个 gRPC 服务
func GetProfilePage(ctx, req) (*ProfileVO, error) {
// 并行调三个服务
resume, _ := resumeClient.GetResume(ctx, req)
apply, _ := applyClient.GetApplications(ctx, req.UserId)
view, _ := statClient.GetProfileViews(ctx, req.UserId)
// 组装前端要的结构(裁剪字段)
return &ProfileVO{
Resume: resume,
ApplyCount: len(apply),
Views: view.Count,
}, nil
}
|
关键:
五、踩坑记录#
- BFF 变成业务层:聚合逻辑越写越多,最后 BFF 重写了业务 → BFF 只做"组装和裁剪",业务在服务层
- 聚合串行:3 个 gRPC 调用串行,页面延迟 = 3 倍 → 并行 + 超时控制(总超时 500ms)
- 网关鉴权重复:网关鉴权了,BFF 又鉴权 → 双层鉴权(网关验 token,BFF 验上下文,职责不同)
- gRPC 错误映射:gRPC status 直接透传给前端(看不懂)→ 映射为 HTTP 状态码 + 业务错误码
六、什么时候不需要 BFF#
gRPC 网关与 BFF 的核心认知:
- 网关管横切(鉴权/限流/审计),BFF 管聚合(页面组装/裁剪)
- gRPC 网关自动转换协议:proto 注解生成 REST 映射
- BFF 是"薄层":只组装裁剪,别写业务
- 按需引入:单端简单接口不需要 BFF
对外接口层是微服务的"门面":网关+BFF 分层,让内部 gRPC 随便演进,前端 API 稳定不变——解耦的价值就在这里。
微信公众号「福清而不淡」
后端架构 · AI 工程 · 云原生的一线实践,扫码关注,不错过更新。
本文已同步发布到公众号,欢迎留言交流。