最近重构了一个带 AI 功能的后台,前端同学跟我吐槽:调个智能体要先拿 token、再传一堆参数、还得自己拼会话上下文,文档翻得头大。我听完决定给他们包一层 BFF,结论是:前端最后只调一个接口。
BFF 解决的是「关注点错配」
Backend For Frontend 这词听着玄,本质就一句话:在前端和一堆后端能力之间,加一个专为前端服务的薄层。
之前前端要做的事:
- 自己存 / 传 conversationId 维护多轮上下文
- 自己处理鉴权 token 的获取和刷新
- 智能体返回的字段一堆 snake_case,前端还要手动转 camelCase
- 上游限流了、超时了,前端各种 if-else 兜底
这些全是后端的活儿,却漏到了前端。BFF 就是把这些收回来。
一个接口长啥样
我的设计是前端只认一个 POST /bff/assistant/ask,请求体干净到只有两个字段:
csharp
// 前端视角:清爽
interface AskRequest {
sessionId: string // 前端只需维护这一个
message: string
}
BFF 内部干的脏活:
dart
app.post('/bff/assistant/ask', async (req, res) => {
const { sessionId, message } = req.body
// 1. 上下文:BFF 自己从 Redis 取历史,前端不用管
const history = await ctxStore.get(sessionId)
// 2. 鉴权:BFF 持有上游 key,前端永远拿不到
const upstreamPayload = buildPayload(history, message)
// 3. 调上游智能体能力
const raw = await callAgent(upstreamPayload)
// 4. 字段裁剪 + 命名转换,只把前端要的字段吐出去
const clean = pick(raw, ['answer', 'references', 'usage'])
// 5. 顺手把这轮存回上下文
await ctxStore.append(sessionId, message, clean.answer)
res.json(toCamel(clean))
})
前端拿到的就是规规整整的 { answer, references, usage },多轮、鉴权、转换全在 BFF 里消化了。
真实的取舍:别把 BFF 写成大杂烩
吹了半天,说点不好的。BFF 最容易腐化成一个啥逻辑都往里塞的垃圾桶,时间一长就成了新的巨石。我的纪律是:BFF 只做编排和裁剪,不做业务决策。比如「这个用户能不能用 AI 功能」这种权限判断,我放在更下游的领域服务里,BFF 只负责把它的结果透传,不自己实现一套。
另一个坑:BFF 加了一跳,链路追踪得跟上。我吃过一次亏------出问题分不清是 BFF 慢还是上游慢。后来给每个请求带个 traceId 一路透传,排查才有了抓手。
这套为啥成立
成立的前提,是上游那个智能体能力本身就是个规整的 HTTP 接口------我用的是那类零代码搭智能体、直接发布成 API 的平台,能力是现成的,我才有底气在前面包薄薄一层。换句话说,讯飞这种 MaaS 把「模型 + 编排」打包成接口,BFF 才能只管编排不管算力。
你们的 BFF 边界划在哪?评论区掰扯掰扯。