Notes

BFF 与 API 设计:前端架构师的视角

一句话结论:采用 BFF (Backend for Frontend) 模式,并遵循 RESTful 或 GraphQL 等成熟的接口规范,设计出具备良好缓存策略、一致性错误处理和明确鉴权模式的 API,是实现前后端高效协作、提升应用整体性能与体验的关键。


1. BFF (Backend for Frontend) 模式

BFF 是一种架构模式,它在通用的后端服务(Microservices 或单体服务)与前端应用之间增加了一个专门的中间层。这个中间层的核心职责是服务于特定的前端体验

+-----------+      +-------------------+      +-----------------+
| Mobile App|----->| Mobile BFF        |----->|                 |
+-----------+      +-------------------+      |                 |
                                              |  Generic Backend  |
+-----------+      +-------------------+      |   (Services)    |
| Web App   |----->| Web BFF           |----->|                 |
+-----------+      +-------------------+      |                 |

A. BFF 的价值

  1. 聚合与裁剪数据:前端页面通常需要来自多个后端微服务的数据。BFF 可以将这些请求聚合起来,一次性返回给前端,减少网络往返。同时,它可以裁剪掉前端不需要的字段,减小 payload 体积。
  2. 适配前端模型:后端服务的数据模型可能与前端需要展示的视图模型不匹配。BFF 负责进行数据转换、格式化和组装,为前端提供“即插即用”的数据。
  3. 减轻前端负担:将一些复杂的、与 UI 无关的逻辑(如数据计算、业务规则验证)从前端移到 BFF,使前端更专注于 UI 渲染。
  4. 技术异构与协议转换:BFF 可以作为不同技术栈之间的桥梁。例如,后端服务使用 gRPC,而 BFF 可以通过 REST 或 GraphQL API 将其暴露给前端。
  5. 增强安全性:BFF 可以作为一道安全屏障,将敏感的 API 密钥或复杂的认证逻辑对前端隐藏。

B. 谁来开发 BFF?

理想情况下,BFF 由前端团队全栈团队拥有和开发。因为 BFF 的需求直接来源于前端,由最理解前端需求的团队来开发,可以最大化其价值,减少跨团队沟通成本。像 Next.js 这样的全栈框架,其 app/api/pages/api/ 目录就是实现 BFF 的绝佳场所。


2. API 接口规范

无论是否使用 BFF,一套清晰、一致的接口规范都是高效协作的基础。

A. RESTful API

REST (Representational State Transfer) 是一套广为接受的 API 设计风格。

  • 资源 (Resources):通过 URL 路径来识别资源,如 /users, /users/123
  • HTTP 动词 (Verbs):使用标准的 HTTP 方法来操作资源。
    • GET /users: 获取用户列表
    • GET /users/123: 获取单个用户
    • POST /users: 创建一个新用户
    • PUT /users/123: 完整替换一个用户
    • PATCH /users/123: 部分更新一个用户
    • DELETE /users/123: 删除一个用户
  • 状态码 (Status Codes):使用标准的 HTTP 状态码来表示请求结果。
    • 2xx (成功): 200 OK, 201 Created, 204 No Content
    • 4xx (客户端错误): 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
    • 5xx (服务器错误): 500 Internal Server Error

B. GraphQL

GraphQL 是一种用于 API 的查询语言和运行时。

  • 强类型:所有 API 都通过一个 schema 来定义,类型安全。
  • 客户端决定数据:客户端可以精确地指定它需要哪些数据,不多也不少,解决了 REST 中 over-fetching 和 under-fetching 的问题。
  • 单一端点:通常只有一个端点(如 /graphql),所有请求都通过 POST 发送到这里。

REST vs. GraphQL

  • REST 更简单,更符合 HTTP 语义,缓存机制更成熟。适合资源结构相对固定的场景。
  • GraphQL 更灵活,更高效,尤其适合移动端或前端需求多变的复杂应用。

C. 一致的响应结构

无论使用哪种规范,都应该定义一个全局一致的 JSON 响应结构,特别是对于错误响应。

成功响应

{
  "data": {
    "id": 123,
    "name": "John Doe"
  },
  "success": true
}

错误响应

{
  "success": false,
  "error": {
    "code": "INVALID_INPUT",
    "message": "Email address is not valid."
  }
}

这种一致性使得在前端封装统一的请求客户端和错误处理器变得非常容易。


3. 鉴权 (Authentication)

API 的鉴权模式必须清晰且统一。

  • 公开端点 (Public):无需任何鉴权的端点。
  • 认证端点 (Authenticated):需要用户登录才能访问的端点。
  • 授权 (Authorization):即使用户已登录,也需要检查其是否有权限访问特定资源(例如,一个普通用户不能访问管理员才能访问的 /admin/dashboard API)。

如上一篇文档所述,基于 JWT 的 Bearer Token 方案是目前的主流。所有需要认证的端点都应该校验 Authorization 头中的 JWT,并从中解析出用户 ID。


4. 缓存 (Caching)

高效的 API 缓存是提升性能、降低服务器成本的关键。

A. HTTP 缓存 (用于 RESTful API)

  • Cache-Control
    • 对于不经常变化的 GET 请求(如获取配置信息、商品列表),可以设置一个较短的缓存时间:Cache-Control: public, max-age=60(缓存 60 秒)。
    • 对于用户特定的数据,使用 private: Cache-Control: private, max-age=60
  • ETag / Last-Modified
    • 服务器为资源生成一个 ETag (内容的 hash)。
    • 客户端在下一次请求时,带上 If-None-Match: <ETag> 头。
    • 如果服务器发现内容未变,返回一个空的 304 Not Modified 响应,客户端则使用本地缓存。这极大地节省了带宽。

B. 应用层缓存

  • 客户端缓存:像 TanStack Query 这样的库,在客户端内存中提供了强大的 API 缓存能力(缓存、失效、后台刷新)。这是提升前端感知性能的最直接手段。
  • BFF/服务器端缓存:对于一些计算成本高、但结果相对稳定的 API,可以在 BFF 或后端服务中使用 Redis 等内存数据库进行缓存。

缓存策略:需要根据数据的特性来决定。是所有用户共享的数据,还是用户私有的数据?数据的更新频率如何?可以接受多大程度的延迟(stale)?


5. 关联阅读

cd ..