BFF 与 API 网关

What — 是什么

BFF(Backend for Frontend)是为特定前端定制的后端服务层,负责数据聚合、接口编排和协议转换。API 网关是所有 API 请求的统一入口,负责路由、鉴权、限流和监控。

核心概念:

  • BFF 模式:每种前端(Web/App/小程序)对应一个专用 BFF,聚合多个后端微服务的数据,按前端需求裁剪格式
  • API 网关:所有外部请求的统一入口,负责路由转发、认证授权、限流、日志、协议转换
  • 接口编排:BFF 聚合多个后端 API 的数据,减少前端请求次数
  • 鉴权下沉:网关层统一处理认证,后端服务无需重复验证
  • GraphQL BFF:用 GraphQL 作为 BFF 层,前端按需查询,BFF 自动聚合

关键特性:

  • BFF 与前端 1:1 对应,按前端需求定制接口
  • API 网关与后端服务 N:1,所有服务共享网关
  • BFF 关注数据聚合和格式适配,网关关注流量治理和安全
  • 两者可合并在同一层(小型项目)

Why — 为什么

适用场景:

  • 多端适配:同一后端服务需为 Web/App/小程序提供不同格式数据
  • 微服务聚合:前端一次请求需调用多个微服务
  • 统一鉴权:所有 API 请求在网关层验证身份
  • 接口版本管理:不同版本的路由和适配

对比架构模式:

维度直连后端BFFAPI 网关
前端请求数多(N个接口)少(1个聚合接口)不变
后端耦合
鉴权每个服务各自实现BFF 层网关层统一
适用规模小项目中大型大型/微服务

优缺点:

  • ✅ 优点:减少前端请求、后端解耦、统一鉴权、接口适配灵活
  • ❌ 缺点:增加一层服务维护成本、BFF 可能成为性能瓶颈

How — 怎么用

快速上手

// BFF 数据聚合
app.get('/api/home', async (req, res) => {
    const [user, orders, notifications] = await Promise.all([
        userService.getProfile(req.user.id),
        orderService.getRecent(req.user.id, 5),
        notificationService.getUnread(req.user.id)
    ]);
    res.json({ user, orders, notifications });
});

// API 网关鉴权中间件
app.use('/api/*', authMiddleware, rateLimitMiddleware);
app.use('/api/users', proxy('http://user-service:3001'));
app.use('/api/orders', proxy('http://order-service:3002'));

代码示例

BFF 聚合层 + 缓存:

const express = require('express');
const Redis = require('ioredis');
const redis = new Redis();

const app = express();

// 多端 BFF:Web 端
app.get('/web/home', async (req, res) => {
    const userId = req.user.id;
    const cacheKey = `bff:web:home:${userId}`;

    const cached = await redis.get(cacheKey);
    if (cached) return res.json(JSON.parse(cached));

    const [profile, feed, recommendations] = await Promise.all([
        fetch(`http://user-svc/profile/${userId}`).then(r => r.json()),
        fetch(`http://content-svc/feed/${userId}?limit=20`).then(r => r.json()),
        fetch(`http://rec-svc/recommend/${userId}?limit=10`).then(r => r.json())
    ]);

    const result = {
        user: { name: profile.name, avatar: profile.avatar },
        feed: feed.items.map(i => ({ id: i.id, title: i.title, summary: i.content.slice(0, 100) })),
        recommendations
    };

    await redis.set(cacheKey, JSON.stringify(result), 'EX', 60);
    res.json(result);
});

// App 端(不同格式)
app.get('/app/home', async (req, res) => {
    const result = await aggregateAppHome(req.user.id);
    res.json(result); // 不同的数据结构和字段
});

API 网关路由 + 鉴权:

const { createProxyMiddleware } = require('http-proxy-middleware');

// 统一鉴权
app.use('/api', verifyToken, (req, res, next) => {
    req.headers['X-User-Id'] = req.user.id;
    req.headers['X-User-Role'] = req.user.role;
    next();
});

// 限流
app.use('/api', rateLimit({ windowMs: 60000, max: 100 }));

// 路由转发
app.use('/api/users', createProxyMiddleware({
    target: 'http://user-service:3001',
    changeOrigin: true,
    pathRewrite: { '^/api/users': '/users' }
}));

app.use('/api/orders', createProxyMiddleware({
    target: 'http://order-service:3002',
    changeOrigin: true,
    pathRewrite: { '^/api/orders': '/orders' }
}));

常见问题与踩坑

问题原因解决方案
BFF 成为瓶颈聚合请求串行执行Promise.all 并行 + 缓存
网关单点故障所有流量经过网关网关集群 + 负载均衡
BFF 与后端接口耦合后端变更影响 BFF定义稳定的后端 API 契约
超时级联多个后端请求串行等待设超时 + 降级(部分数据缺失仍返回)

最佳实践

  • BFF 层使用 Promise.all 并行请求后端服务
  • 关键聚合结果缓存到 Redis,设置短 TTL
  • 网关统一处理认证,后端只校验 X-User-Id Header
  • BFF 实现降级策略:部分后端不可用时返回可用数据
  • 网关做限流,保护后端服务不被突发流量打垮

面试题

Q1: BFF 模式与传统 API 有什么区别?

传统 API:前端直接调用后端微服务,一个页面可能需要 5-10 个请求,数据格式由后端决定。BFF 模式:前端调用专用 BFF 层,BFF 聚合多个后端服务的数据,一次请求返回前端需要的所有数据,格式按前端定制。核心区别:① 请求次数从 N 减到 1;② 数据格式由前端需求决定而非后端定义;③ 每种前端有自己的 BFF,互不影响;④ 后端微服务只提供原子能力,不关心前端展示。

Q2: API 网关的核心职责?与 BFF 的区别?

网关职责:路由转发、认证授权、限流熔断、日志监控、协议转换、灰度发布。BFF 职责:数据聚合、格式适配、字段裁剪。区别:网关是流量层面的治理(不关心数据内容),BFF 是业务层面的编排(关心数据如何组合)。网关对所有服务通用,BFF 对特定前端定制。可以共存:前端 → 网关(鉴权/限流)→ BFF(聚合)→ 微服务。

Q3: BFF 如何实现降级策略?

降级策略:① 核心数据优先——关键数据(用户信息/订单)必须返回,辅助数据(推荐/评论)允许缺失;② 超时降级——每个后端请求设超时(如 2 秒),超时返回空值或缓存数据;③ 缓存兜底——后端不可用时返回 Redis 缓存的旧数据;④ 部分返回——Promise.allSettled 收集成功和失败结果,成功的正常返回,失败的设为 null 或默认值,标记 degraded: true

Q4: GraphQL 作为 BFF 有什么优势?

GraphQL BFF 优势:① 按需获取——前端声明需要哪些字段,BFF 只返回请求的字段,无过度获取/获取不足;② 自动聚合——GraphQL 的 Resolver 天然并行执行,多个后端数据自动聚合;③ 强类型 Schema——前后端共享类型定义,编译时检查;④ 自文档化——Schema 即文档,配合 Playground 可视化调试。劣势:学习曲线、N+1 查询风险(需 Dataloader)、缓存不如 REST 直观。


相关链接: