4. BFF 层职责与边界:BFF 与后端服务的关系、BFF 与前端的关系、数据聚合与裁剪、会话管理边界

在微服务架构中,BFF(Backend For Frontend)层是前端与后端服务之间的“中间人”。明确 BFF 的职责与边界,是保障系统可维护性、避免职责混乱的关键。本章从四个核心维度展开:BFF 与后端服务的关系、BFF 与前端的关系、数据聚合与裁剪、会话管理边界。

4.1 BFF 与后端服务的关系

BFF 是后端服务的“编排者”与“代理”,而非“替代者”。BFF 不包含核心业务逻辑,也不直接操作数据库。其核心职责包括:

  • 服务路由与聚合:将前端的一个请求,拆解为对多个后端微服务的调用,并聚合结果。
  • 协议转换:将后端的 gRPC/Thrift/AMQP 协议转换为前端友好的 HTTP/REST 或 GraphQL。
  • 错误处理与降级:对后端服务的超时、异常进行统一处理,返回前端可理解的错误结构。
  • 限流与鉴权前置:在 BFF 层完成 Token 校验、频率限制,避免无效请求穿透到后端。
维度 BFF 层 后端服务
业务逻辑 仅编排,不实现核心业务 实现领域核心逻辑
数据持久化 不直接访问数据库 负责数据读写
协议 HTTP/GraphQL(面向前端) gRPC/消息队列(内部)
状态 无状态或轻量会话 有状态(数据库/缓存)
部署频率 随前端需求频繁变更 按领域版本发布
实践建议:BFF 层应避免引入 ORM 或数据库连接池。一旦 BFF 开始写 SQL,就说明边界已经模糊,应重构为独立的后端服务。

4.2 BFF 与前端的关系

BFF 是前端的“专属适配层”。每个前端客户端(Web、iOS、Android、小程序)通常拥有独立的 BFF 实例,以确保:

  • 接口契约独立:不同前端对数据格式、字段粒度、响应时间的要求不同,BFF 按需定制。
  • 减少前端复杂度:前端无需关心后端服务拆分细节,只需调用 BFF 提供的聚合接口。
  • 版本解耦:后端服务升级时,BFF 负责适配,前端代码无需同步修改。

BFF 与前端之间的通信应遵循以下原则:

  1. 接口粒度以页面/组件为单位:一个页面请求对应一个 BFF 接口,避免前端发起多次网络请求。
  2. 响应结构扁平化:BFF 返回的数据应直接可渲染,避免前端做深层数据转换。
  3. 错误码统一:BFF 将后端各种错误码映射为前端可展示的提示文案或状态码。
边界红线:BFF 不应包含 UI 逻辑(如按钮显隐、排序规则),这些应保留在前端。BFF 只负责“提供数据”,不负责“决定如何展示”。

4.3 数据聚合与裁剪

数据聚合与裁剪是 BFF 最核心的“增值”能力。聚合解决“如何拼”,裁剪解决“给多少”。

4.3.1 数据聚合模式

  • 并行聚合:同时调用多个无依赖的后端服务,使用 sync.WaitGroup 或 errgroup 并发等待。
  • 串行聚合:后一个请求依赖前一个请求的结果,如先查用户 ID,再查订单列表。
  • 混合聚合:部分并行、部分串行,适用于复杂页面。

4.3.2 数据裁剪策略

  • 字段白名单:BFF 只返回前端需要的字段,避免传输冗余数据。
  • 分页与截断:对列表类数据强制分页,防止一次返回过多数据。
  • 敏感字段过滤:在 BFF 层移除密码、手机号等敏感信息,确保前端无法获取。
// Go 示例:BFF 层数据聚合与裁剪
type UserOrderResponse struct {
    UserID    string `json:"user_id"`
    UserName  string `json:"user_name"`
    Orders    []Order `json:"orders"`
    TotalPage int    `json:"total_page"`
}

func (b *BFFService) GetUserOrders(ctx context.Context, userID string, page int) (*UserOrderResponse, error) {
    // 1. 并行获取用户信息和订单总数
    var userInfo *UserInfo
    var total int64
    g, ctx := errgroup.WithContext(ctx)
    g.Go(func() error {
        var err error
        userInfo, err = b.userSvc.GetUser(ctx, userID)
        return err
    })
    g.Go(func() error {
        var err error
        total, err = b.orderSvc.CountOrders(ctx, userID)
        return err
    })
    if err := g.Wait(); err != nil {
        return nil, err
    }

    // 2. 串行获取分页订单(依赖 total 计算分页)
    orders, err := b.orderSvc.ListOrders(ctx, userID, page, pageSize)
    if err != nil {
        return nil, err
    }

    // 3. 裁剪:只返回前端需要的字段
    return &UserOrderResponse{
        UserID:    userInfo.ID,
        UserName:  userInfo.Name, // 裁剪掉 phone, email
        Orders:    orders,
        TotalPage: int(math.Ceil(float64(total) / float64(pageSize))),
    }, nil
}
常见陷阱:在 BFF 层做数据聚合时,容易忽略超时控制。每个后端调用都应设置独立的超时,且总超时应小于前端等待超时。建议使用 context.WithTimeout 为每个子调用设置超时。

4.4 会话管理边界

会话管理是 BFF 层最容易“越界”的领域。BFF 应管理“会话凭证”,而非“会话状态”。

4.4.1 BFF 的会话职责

  • Token 校验与解析:验证 JWT/Session ID 的合法性,解析用户身份信息。
  • Token 续期:在 Token 即将过期时,自动刷新并设置新的 Cookie/Header。
  • 会话上下文传递:将解析后的用户 ID、角色等信息注入到 gRPC 元数据或 HTTP Header 中,传递给后端服务。

4.4.2 会话管理的边界红线

属于 BFF 不属于 BFF
Token 校验与解析 用户登录/注册(应属于 Auth 服务)
Token 刷新 密码修改/重置
会话上下文透传 会话持久化存储(如 Redis 存 Session)
登出(清除 Cookie) 权限变更通知
最佳实践:BFF 层应使用无状态 JWT,避免在 BFF 内存或本地缓存中维护会话状态。这样 BFF 实例可以随意扩缩容,不会丢失会话。
BFF 层职责边界与数据流 前端 (Web/App) BFF 层 会话校验 & Token 解析 数据聚合 & 裁剪 协议转换 (gRPC→HTTP) 错误处理 & 降级 限流 & 鉴权前置 用户服务 订单服务 商品服务 Auth 服务 HTTP 请求 gRPC gRPC gRPC gRPC 边界原则 BFF 不直接访问数据库,不实现核心业务逻辑,不存储会话状态。 BFF 只做编排、转换、裁剪、校验,所有“写数据库”操作必须由后端服务完成。

4.5 边界总结:BFF 的“该做”与“不该做”

职责域 该做(BFF 职责) 不该做(越界行为)
与后端关系 服务编排、协议转换、错误聚合 直接操作数据库、实现业务规则
与前端关系 接口定制、数据裁剪、版本适配 包含 UI 逻辑、决定组件显隐
数据聚合 并行/串行调用、字段白名单 数据排序、复杂计算、数据持久化
会话管理 Token 校验、解析、透传 密码管理、会话持久化、权限变更
核心原则:BFF 是“薄层”,越薄越稳定。每次在 BFF 中增加新逻辑前,先问自己:“这个逻辑是否应该放在后端服务中?” 如果答案是“是”,则坚决不放入 BFF。